/* ChibiOS - Copyright (C) 2006..2016 Giovanni Di Sirio Licensed 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. */ /** * @file irq_storm.c * @brief IRQ Storm stress test code. * * @addtogroup IRQ_STORM * @{ */ #include #include "ch.h" #include "hal.h" #include "chprintf.h" #include "irq_storm.h" /*===========================================================================*/ /* Module local definitions. */ /*===========================================================================*/ #define MSG_SEND_LEFT (msg_t)0 #define MSG_SEND_RIGHT (msg_t)1 /*===========================================================================*/ /* Module exported variables. */ /*===========================================================================*/ /*===========================================================================*/ /* Module local types. */ /*===========================================================================*/ /*===========================================================================*/ /* Module local variables. */ /*===========================================================================*/ static const irq_storm_config_t *config; static bool saturated; /* * Mailboxes and buffers. */ static mailbox_t mb[IRQ_STORM_CFG_NUM_THREADS]; static msg_t b[IRQ_STORM_CFG_NUM_THREADS][IRQ_STORM_CFG_MAILBOX_SIZE]; /* * Threads working areas. */ static THD_WORKING_AREA(irq_storm_thread_wa[IRQ_STORM_CFG_NUM_THREADS], IRQ_STORM_CFG_STACK_SIZE); /* * Pointers to threads. */ static thread_t *threads[IRQ_STORM_CFG_NUM_THREADS]; /*===========================================================================*/ /* Module local functions. */ /*===========================================================================*/ /*===========================================================================*/ /* Module exported functions. */ /*===========================================================================*/ /* * Test worker threads. */ static THD_FUNCTION(irq_storm_thread, arg) { static volatile unsigned x = 0; static unsigned cnt = 0; unsigned me = (unsigned)arg; unsigned target; unsigned r; msg_t msg; chRegSetThreadName("irq_storm"); /* Thread loop, until terminated.*/ while (chThdShouldTerminateX() == false) { /* Waiting for a message.*/ chMBFetch(&mb[me], &msg, TIME_INFINITE); #if IRQ_STORM_CFG_RANDOMIZE != FALSE /* Pseudo-random delay.*/ { chSysLock(); r = rand() & 15; chSysUnlock(); while (r--) x++; } #else /* IRQ_STORM_CFG_RANDOMIZE == FALSE */ /* Fixed delay.*/ { r = me >> 4; while (r--) x++; } #endif /* IRQ_STORM_CFG_RANDOMIZE == FALSE */ /* Deciding in which direction to re-send the message.*/ if (msg == MSG_SEND_LEFT) target = me - 1; else target = me + 1; if (target < IRQ_STORM_CFG_NUM_THREADS) { /* If this thread is not at the end of a chain re-sending the message, note this check works because the variable target is unsigned.*/ msg = chMBPost(&mb[target], msg, TIME_IMMEDIATE); if (msg != MSG_OK) saturated = TRUE; } else { /* Provides a visual feedback about the system.*/ if (++cnt >= 500) { cnt = 0; palTogglePad(config->port, config->pad); } } } } /** * @brief GPT1 callback. */ void irq_storm_gpt1_cb(GPTDriver *gptp) { msg_t msg; (void)gptp; chSysLockFromISR(); msg = chMBPostI(&mb[0], MSG_SEND_RIGHT); if (msg != MSG_OK) saturated = true; chSysUnlockFromISR(); } /** * @brief GPT2 callback. */ void irq_storm_gpt2_cb(GPTDriver *gptp) { msg_t msg; (void)gptp; chSysLockFromISR(); msg = chMBPostI(&mb[IRQ_STORM_CFG_NUM_THREADS - 1], MSG_SEND_LEFT); if (msg != MSG_OK) saturated = true; chSysUnlockFromISR(); } /** * @brief IRQ storm execution. * * @param[in] cfg pointer to the test configuration structure * * @api */ void irq_storm_execute(const irq_storm_config_t *cfg) { unsigned i; gptcnt_t interval, threshold, worst; /* Global configuration pointer.*/ config = cfg; /* Starting timers using the stored configurations.*/ gptStart(cfg->gpt1p, cfg->gptcfg1p); gptStart(cfg->gpt2p, cfg->gptcfg2p); /* * Initializes the mailboxes and creates the worker threads. */ for (i = 0; i < IRQ_STORM_CFG_NUM_THREADS; i++) { chMBObjectInit(&mb[i], b[i], IRQ_STORM_CFG_MAILBOX_SIZE); threads[i] = chThdCreateStatic(irq_storm_thread_wa[i], sizeof irq_storm_thread_wa[i], IRQ_STORM_CFG_THREADS_PRIORITY, irq_storm_thread, (void *)i); } /* Printing environment information.*/ chprintf(cfg->out, ""); chprintf(cfg->out, "\r\n*** ChibiOS/RT IRQ-STORM long duration test\r\n***\r\n"); chprintf(cfg->out, "*** Kernel: %s\r\n", CH_KERNEL_VERSION); chprintf(cfg->out, "*** Compiled: %s\r\n", __DATE__ " - " __TIME__); #ifdef PORT_COMPILER_NAME chprintf(cfg->out, "*** Compiler: %s\r\n", PORT_COMPILER_NAME); #endif chprintf(cfg->out, "*** Architecture: %s\r\n", PORT_ARCHITECTURE_NAME); #ifdef PORT_CORE_VARIANT_NAME chprintf(cfg->out, "*** Core Variant: %s\r\n", PORT_CORE_VARIANT_NAME); #endif chprintf(cfg->out, "*** System Clock: %d\r\n", cfg->sysclk); #ifdef PORT_INFO chprintf(cfg->out, "*** Port Info: %s\r\n", PORT_INFO); #endif #ifdef PLATFORM_NAME chprintf(cfg->out, "*** Platform: %s\r\n", PLATFORM_NAME); #endif #ifdef BOARD_NAME chprintf(cfg->out, "*** Test Board: %s\r\n", BOARD_NAME); #endif chprintf(cfg->out, "***\r\n"); chprintf(cfg->out, "*** Iterations: %d\r\n", IRQ_STORM_CFG_ITERATIONS); chprintf(cfg->out, "*** Randomize: %d\r\n", IRQ_STORM_CFG_RANDOMIZE); chprintf(cfg->out, "*** Threads: %d\r\n", IRQ_STORM_CFG_NUM_THREADS); chprintf(cfg->out, "*** Mailbox size: %d\r\n\r\n", IRQ_STORM_CFG_MAILBOX_SIZE); /* Test loop.*/ worst = 0; for (i = 1; i <= IRQ_STORM_CFG_ITERATIONS; i++){ chprintf(cfg->out, "Iteration %d\r\n", i); saturated = false; threshold = 0; /* Timer intervals starting at 2mS then decreased by 10% after each cycle.*/ for (interval = 2000; interval >= 2; interval -= (interval + 9) / 10) { /* Timers programmed slightly out of phase each other.*/ gptStartContinuous(cfg->gpt1p, interval - 1); /* Slightly out of phase.*/ gptStartContinuous(cfg->gpt2p, interval + 1); /* Slightly out of phase.*/ /* Storming for one second.*/ chThdSleepMilliseconds(1000); /* Timers stopped.*/ gptStopTimer(cfg->gpt1p); gptStopTimer(cfg->gpt2p); /* Did the storm saturate the threads chain?*/ if (!saturated) chprintf(cfg->out, "."); else { chprintf(cfg->out, "#"); if (threshold == 0) threshold = interval; break; } } /* Gives threads a chance to empty the mailboxes before next cycle.*/ chThdSleepMilliseconds(20); chprintf(cfg->out, "\r\nSaturated at %d uS\r\n\r\n", threshold); if (threshold > worst) worst = threshold; } gptStopTimer(cfg->gpt1p); gptStopTimer(cfg->gpt2p); chprintf(cfg->out, "Worst case at %d uS\r\n", worst); chprintf(cfg->out, "\r\nTest Complete\r\n"); /* Terminating threads and cleaning up.*/ for (i = 0; i < IRQ_STORM_CFG_NUM_THREADS; i++) { chThdTerminate(threads[i]); chThdWait(threads[i]); threads[i] = NULL; } } /** @} */ '>173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
/*
Copyright 2012 Jun Wako <wakojun@gmail.com>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#include <stdbool.h>
#include "util.h"
#include "matrix.h"
#include "debounce.h"
#include "quantum.h"
#include "split_util.h"
#include "config.h"
#include "transport.h"

#ifdef ENCODER_ENABLE
#    include "encoder.h"
#endif

#define ERROR_DISCONNECT_COUNT 5

#define ROWS_PER_HAND (MATRIX_ROWS / 2)

#ifdef DIRECT_PINS
static pin_t direct_pins[MATRIX_ROWS][MATRIX_COLS] = DIRECT_PINS;
#elif (DIODE_DIRECTION == ROW2COL) || (DIODE_DIRECTION == COL2ROW)
static pin_t row_pins[MATRIX_ROWS] = MATRIX_ROW_PINS;
static pin_t col_pins[MATRIX_COLS] = MATRIX_COL_PINS;
#endif

/* matrix state(1:on, 0:off) */
extern matrix_row_t raw_matrix[MATRIX_ROWS];  // raw values
extern matrix_row_t matrix[MATRIX_ROWS];      // debounced values

// row offsets for each hand
uint8_t thisHand, thatHand;

// user-defined overridable functions
__attribute__((weak)) void matrix_slave_scan_user(void) {}

// matrix code

#ifdef DIRECT_PINS

static void init_pins(void) {
    for (int row = 0; row < MATRIX_ROWS; row++) {
        for (int col = 0; col < MATRIX_COLS; col++) {
            pin_t pin = direct_pins[row][col];
            if (pin != NO_PIN) {
                setPinInputHigh(pin);
            }
        }
    }
}

static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) {
    matrix_row_t last_row_value = current_matrix[current_row];
    current_matrix[current_row] = 0;

    for (uint8_t col_index = 0; col_index < MATRIX_COLS; col_index++) {
        pin_t pin = direct_pins[current_row][col_index];
        if (pin != NO_PIN) {
            current_matrix[current_row] |= readPin(pin) ? 0 : (MATRIX_ROW_SHIFTER << col_index);
        }
    }

    return (last_row_value != current_matrix[current_row]);
}

#elif defined(DIODE_DIRECTION)
#    if (DIODE_DIRECTION == COL2ROW)

static void select_row(uint8_t row) {
    setPinOutput(row_pins[row]);
    writePinLow(row_pins[row]);
}

static void unselect_row(uint8_t row) { setPinInputHigh(row_pins[row]); }

static void unselect_rows(void) {
    for (uint8_t x = 0; x < ROWS_PER_HAND; x++) {
        setPinInputHigh(row_pins[x]);
    }
}

static void init_pins(void) {
    unselect_rows();
    for (uint8_t x = 0; x < MATRIX_COLS; x++) {
        setPinInputHigh(col_pins[x]);
    }
}

static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) {
    // Store last value of row prior to reading
    matrix_row_t last_row_value = current_matrix[current_row];

    // Clear data in matrix row
    current_matrix[current_row] = 0;

    // Select row and wait for row selecton to stabilize
    select_row(current_row);
    matrix_io_delay();

    // For each col...
    for (uint8_t col_index = 0; col_index < MATRIX_COLS; col_index++) {
        // Select the col pin to read (active low)
        uint8_t pin_state = readPin(col_pins[col_index]);

        // Populate the matrix row with the state of the col pin
        current_matrix[current_row] |= pin_state ? 0 : (MATRIX_ROW_SHIFTER << col_index);
    }

    // Unselect row
    unselect_row(current_row);

    return (last_row_value != current_matrix[current_row]);
}

#    elif (DIODE_DIRECTION == ROW2COL)

static void select_col(uint8_t col) {
    setPinOutput(col_pins[col]);
    writePinLow(col_pins[col]);
}

static void unselect_col(uint8_t col) { setPinInputHigh(col_pins[col]); }

static void unselect_cols(void) {
    for (uint8_t x = 0; x < MATRIX_COLS; x++) {
        setPinInputHigh(col_pins[x]);
    }
}

static void init_pins(void) {
    unselect_cols();
    for (uint8_t x = 0; x < ROWS_PER_HAND; x++) {
        setPinInputHigh(row_pins[x]);
    }
}

static bool read_rows_on_col(matrix_row_t current_matrix[], uint8_t current_col) {
    bool matrix_changed = false;

    // Select col and wait for col selecton to stabilize
    select_col(current_col);
    matrix_io_delay();

    // For each row...
    for (uint8_t row_index = 0; row_index < ROWS_PER_HAND; row_index++) {
        // Store last value of row prior to reading
        matrix_row_t last_row_value = current_matrix[row_index];

        // Check row pin state
        if (readPin(row_pins[row_index]) == 0) {
            // Pin LO, set col bit
            current_matrix[row_index] |= (MATRIX_ROW_SHIFTER << current_col);
        } else {
            // Pin HI, clear col bit
            current_matrix[row_index] &= ~(MATRIX_ROW_SHIFTER << current_col);
        }

        // Determine if the matrix changed state
        if ((last_row_value != current_matrix[row_index]) && !(matrix_changed)) {
            matrix_changed = true;
        }
    }

    // Unselect col
    unselect_col(current_col);

    return matrix_changed;
}

#    else
#        error DIODE_DIRECTION must be one of COL2ROW or ROW2COL!
#    endif
#else
#    error DIODE_DIRECTION is not defined!
#endif

void matrix_init(void) {
    split_pre_init();

    // Set pinout for right half if pinout for that half is defined
    if (!isLeftHand) {
#ifdef DIRECT_PINS_RIGHT
        const pin_t direct_pins_right[MATRIX_ROWS][MATRIX_COLS] = DIRECT_PINS_RIGHT;
        for (uint8_t i = 0; i < MATRIX_ROWS; i++) {
            for (uint8_t j = 0; j < MATRIX_COLS; j++) {
                direct_pins[i][j] = direct_pins_right[i][j];
            }
        }
#endif
#ifdef MATRIX_ROW_PINS_RIGHT
        const pin_t row_pins_right[MATRIX_ROWS] = MATRIX_ROW_PINS_RIGHT;
        for (uint8_t i = 0; i < MATRIX_ROWS; i++) {
            row_pins[i] = row_pins_right[i];
        }
#endif
#ifdef MATRIX_COL_PINS_RIGHT
        const pin_t col_pins_right[MATRIX_COLS] = MATRIX_COL_PINS_RIGHT;
        for (uint8_t i = 0; i < MATRIX_COLS; i++) {
            col_pins[i] = col_pins_right[i];
        }
#endif
    }

    thisHand = isLeftHand ? 0 : (ROWS_PER_HAND);
    thatHand = ROWS_PER_HAND - thisHand;

    // initialize key pins
    init_pins();

    // initialize matrix state: all keys off
    for (uint8_t i = 0; i < MATRIX_ROWS; i++) {
        raw_matrix[i] = 0;
        matrix[i]     = 0;
    }

    debounce_init(ROWS_PER_HAND);

    matrix_init_quantum();

    split_post_init();
}

void matrix_post_scan(void) {
    if (is_keyboard_master()) {
        static uint8_t error_count;

        if (!transport_master(matrix + thatHand)) {
            error_count++;

            if (error_count > ERROR_DISCONNECT_COUNT) {
                // reset other half if disconnected
                for (int i = 0; i < ROWS_PER_HAND; ++i) {
                    matrix[thatHand + i] = 0;
                }
            }
        } else {
            error_count = 0;
        }

        matrix_scan_quantum();
    } else {
        transport_slave(matrix + thisHand);
#ifdef ENCODER_ENABLE
        encoder_read();
#endif
        matrix_slave_scan_user();
    }
}

uint8_t matrix_scan(void) {
    bool changed = false;

#if defined(DIRECT_PINS) || (DIODE_DIRECTION == COL2ROW)
    // Set row, read cols
    for (uint8_t current_row = 0; current_row < ROWS_PER_HAND; current_row++) {
        changed |= read_cols_on_row(raw_matrix, current_row);
    }
#elif (DIODE_DIRECTION == ROW2COL)
    // Set col, read rows
    for (uint8_t current_col = 0; current_col < MATRIX_COLS; current_col++) {
        changed |= read_rows_on_col(raw_matrix, current_col);
    }
#endif

    debounce(raw_matrix, matrix + thisHand, ROWS_PER_HAND, changed);

    matrix_post_scan();
    return (uint8_t)changed;
}