├── .gitignore ├── Makefile ├── README.md ├── scripts ├── install-git-hooks ├── pre-push.hook ├── aspell-pws ├── pre-commit.hook └── commit-msg.hook ├── .clang-format ├── LICENSE └── simrupt.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.ko 3 | *.mod 4 | *.mod.c 5 | *.mod.o 6 | *.order 7 | *.cmd 8 | *.symvers 9 | user 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME = simrupt 2 | obj-m := $(NAME).o 3 | 4 | KDIR ?= /lib/modules/$(shell uname -r)/build 5 | PWD := $(shell pwd) 6 | 7 | GIT_HOOKS := .git/hooks/applied 8 | all: $(GIT_HOOKS) simrupt.c 9 | $(MAKE) -C $(KDIR) M=$(PWD) modules 10 | 11 | $(GIT_HOOKS): 12 | @scripts/install-git-hooks 13 | @echo 14 | 15 | 16 | clean: 17 | $(MAKE) -C $(KDIR) M=$(PWD) clean 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # simrupt: A device that simulates interrupts 2 | 3 | `simrupt` is a simple Linux kernel module to explain the concept of deferred 4 | work and kernel concepts as following: 5 | - circular buffer 6 | - mutex lock 7 | - irq 8 | - softirq 9 | - tasklet 10 | - workqueue 11 | - kernel thread 12 | 13 | It can be also used as a template to implement an IRQ-based device driver. 14 | 15 | ## License 16 | 17 | `simrupt` is released under the MIT license. Use of this source code is governed 18 | by a MIT-style license that can be found in the LICENSE file. 19 | -------------------------------------------------------------------------------- /scripts/install-git-hooks: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if ! test -d .git; then 4 | echo "Execute scripts/install-git-hooks in the top-level directory." 5 | exit 1 6 | fi 7 | 8 | ln -sf ../../scripts/pre-commit.hook .git/hooks/pre-commit || exit 1 9 | chmod +x .git/hooks/pre-commit 10 | 11 | ln -sf ../../scripts/commit-msg.hook .git/hooks/commit-msg || exit 1 12 | chmod +x .git/hooks/commit-msg 13 | 14 | ln -sf ../../scripts/pre-push.hook .git/hooks/pre-push || exit 1 15 | chmod +x .git/hooks/pre-push 16 | 17 | touch .git/hooks/applied || exit 1 18 | 19 | echo 20 | echo "Git hooks are installed successfully." 21 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Chromium 2 | Language: Cpp 3 | MaxEmptyLinesToKeep: 3 4 | IndentCaseLabels: false 5 | AllowShortIfStatementsOnASingleLine: false 6 | AllowShortCaseLabelsOnASingleLine: false 7 | AllowShortLoopsOnASingleLine: false 8 | DerivePointerAlignment: false 9 | PointerAlignment: Right 10 | SpaceAfterCStyleCast: true 11 | TabWidth: 4 12 | UseTab: Never 13 | IndentWidth: 4 14 | BreakBeforeBraces: Linux 15 | AccessModifierOffset: -4 16 | ForEachMacros: 17 | - foreach 18 | - Q_FOREACH 19 | - BOOST_FOREACH 20 | - list_for_each 21 | - list_for_each_safe 22 | - list_for_each_entry 23 | - list_for_each_entry_safe 24 | - hlist_for_each_entry 25 | - rb_list_foreach 26 | - rb_list_foreach_safe 27 | - vec_foreach 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (C) 2021, 2024 National Cheng Kung University, Taiwan. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /scripts/pre-push.hook: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | protected_branch='master' 4 | current_branch=$(git symbolic-ref HEAD | sed -e 's,.*/\(.*\),\1,') 5 | RED='\033[0;31m' 6 | GREEN='\033[1;32m' 7 | YELLOW='\033[1;33m' 8 | NC='\033[0m' # No Color 9 | 10 | # Show hints 11 | echo -e "${YELLOW}Hint${NC}: You might want to know why Git is always ${GREEN}asking for my password${NC}." 12 | echo -e " https://help.github.com/en/github/using-git/why-is-git-always-asking-for-my-password" 13 | echo "" 14 | 15 | # only run this if you are pushing to master 16 | if [[ $current_branch = $protected_branch ]] ; then 17 | echo -e "${YELLOW}Running pre push to master check...${NC}" 18 | 19 | echo -e "${YELLOW}Trying to build tests project...${NC}" 20 | 21 | # build the project 22 | make 23 | 24 | # $? is a shell variable which stores the return code from what we just ran 25 | rc=$? 26 | if [[ $rc != 0 ]] ; then 27 | echo -e "${RED}Failed to build the project, please fix this and push again${NC}" 28 | echo "" 29 | exit $rc 30 | fi 31 | 32 | # Everything went OK so we can exit with a zero 33 | echo -e "${GREEN}Pre-push check passed!${NC}" 34 | echo "" 35 | fi 36 | 37 | exit 0 38 | -------------------------------------------------------------------------------- /scripts/aspell-pws: -------------------------------------------------------------------------------- 1 | personal_ws-1.1 en 500 2 | usr 3 | lib 4 | sbin 5 | env 6 | bash 7 | etc 8 | var 9 | dudect 10 | runtime 11 | todo 12 | fixme 13 | hotfix 14 | qtest 15 | vscode 16 | sanitizer 17 | unix 18 | linux 19 | valgrind 20 | ubuntu 21 | gdb 22 | sdk 23 | aspell 24 | cppcheck 25 | glibc 26 | git 27 | pre 28 | gcc 29 | clang 30 | enqueue 31 | dequeue 32 | fifo 33 | lifo 34 | stdin 35 | stdout 36 | stderr 37 | strdup 38 | strcmp 39 | strcasecmp 40 | snprintf 41 | sprintf 42 | strcat 43 | strchr 44 | strcmp 45 | strcoll 46 | strcpy 47 | strcspn 48 | strerror 49 | strlen 50 | strncasecmp 51 | strncat 52 | strncmp 53 | strncpy 54 | strpbrk 55 | strrchr 56 | strspn 57 | strstr 58 | strtod 59 | strtof 60 | strtok 61 | strtol 62 | strtold 63 | strtoul 64 | atexit 65 | atof 66 | atoi 67 | atol 68 | bsearch 69 | calloc 70 | fclose 71 | fdopen 72 | feof 73 | ferror 74 | fflush 75 | fgetc 76 | fgetpos 77 | fgets 78 | fileno 79 | fopen 80 | fprintf 81 | fputc 82 | fputs 83 | fread 84 | freopen 85 | fscanf 86 | fseek 87 | fsetpos 88 | ftell 89 | fwrite 90 | getc 91 | getchar 92 | getenv 93 | gets 94 | isalnum 95 | isalpha 96 | isascii 97 | iscntrl 98 | isdigit 99 | isgraph 100 | islower 101 | isprint 102 | ispunct 103 | isspace 104 | isupper 105 | longjmp 106 | memchr 107 | memcmp 108 | memcpy 109 | memmove 110 | memset 111 | printf 112 | putc 113 | putchar 114 | putenv 115 | puts 116 | qsort 117 | rand 118 | realloc 119 | regcomp 120 | regerror 121 | regexec 122 | regfree 123 | rewind 124 | scanf 125 | setbuf 126 | setjmp 127 | signal 128 | srand 129 | sscanf 130 | macOS 131 | Fibonacci 132 | fib 133 | pow 134 | Binet 135 | Vorobev 136 | GMP 137 | MPFR 138 | mutex 139 | trylock 140 | unlock 141 | lseek 142 | llseek 143 | cdev 144 | inode 145 | sysfs 146 | printk 147 | clz 148 | fops 149 | init 150 | alloc 151 | ktime 152 | getres 153 | gettime 154 | settime 155 | ns 156 | timespec 157 | timeval 158 | xor 159 | xoro 160 | xoroshiro 161 | prng 162 | ksort 163 | cmwq 164 | workqueue 165 | spinlock 166 | tasklet 167 | kmalloc 168 | kzalloc 169 | kfree 170 | xoroshiro 171 | xoro 172 | RNG 173 | PRNG 174 | gitignore 175 | fifo 176 | kfifo 177 | PTR 178 | ttt 179 | tictactoe 180 | vmalloc 181 | -------------------------------------------------------------------------------- /scripts/pre-commit.hook: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CPPCHECK_suppresses="--suppress=unmatchedSuppression \ 4 | --suppress=missingIncludeSystem -i ksort.mod.c --suppress=variableScope \ 5 | --suppress=constParameterPointer:simrupt.c \ 6 | --suppress=constParameterCallback:simrupt.c" 7 | CPPCHECK_OPTS="-I. --enable=all --error-exitcode=1 --force $CPPCHECK_suppresses ." 8 | 9 | RETURN=0 10 | CLANG_FORMAT=$(which clang-format) 11 | if [ $? -ne 0 ]; then 12 | echo "[!] clang-format not installed. Unable to check source file format policy." >&2 13 | exit 1 14 | fi 15 | 16 | CPPCHECK=$(which cppcheck) 17 | if [ $? -ne 0 ]; then 18 | echo "[!] cppcheck not installed. Unable to perform static analysis." >&2 19 | exit 1 20 | fi 21 | 22 | ASPELL=$(which aspell) 23 | if [ $? -ne 0 ]; then 24 | echo "[!] aspell not installed. Unable to do spelling check." >&2 25 | exit 1 26 | fi 27 | 28 | DIFF=$(which colordiff) 29 | if [ $? -ne 0 ]; then 30 | DIFF=diff 31 | fi 32 | 33 | FILES=`git diff --cached --name-only --diff-filter=ACMR | grep -E "\.(c|cpp|h)$"` 34 | for FILE in $FILES; do 35 | nf=`git checkout-index --temp $FILE | cut -f 1` 36 | tempdir=`mktemp -d` || exit 1 37 | newfile=`mktemp ${tempdir}/${nf}.XXXXXX` || exit 1 38 | basename=`basename $FILE` 39 | 40 | source="${tempdir}/${basename}" 41 | mv $nf $source 42 | cp .clang-format $tempdir 43 | $CLANG_FORMAT $source > $newfile 2>> /dev/null 44 | $DIFF -u -p -B --label="modified $FILE" --label="expected coding style" \ 45 | "${source}" "${newfile}" 46 | r=$? 47 | rm -rf "${tempdir}" 48 | if [ $r != 0 ] ; then 49 | echo "[!] $FILE does not follow the consistent coding style." >&2 50 | RETURN=1 51 | fi 52 | if [ $RETURN -eq 1 ]; then 53 | echo "" >&2 54 | echo "Make sure you indent as the following:" >&2 55 | echo " clang-format -i $FILE" >&2 56 | echo 57 | fi 58 | done 59 | 60 | # Prevent unsafe functions 61 | root=$(git rev-parse --show-toplevel) 62 | banned="([^f]gets\()|(sprintf\()|(strcpy\()" 63 | status=0 64 | for file in $(git diff --staged --name-only | grep -E "\.(c|cc|cpp|h|hh|hpp)\$") 65 | do 66 | filepath="${root}/${file}" 67 | output=$(grep -nrE "${banned}" "${filepath}") 68 | if [ ! -z "${output}" ]; then 69 | echo "Dangerous function detected in ${filepath}" 70 | echo "${output}" 71 | echo 72 | echo "Read 'Common vulnerabilities guide for C programmers' carefully." 73 | echo " https://security.web.cern.ch/security/recommendations/en/codetools/c.shtml" 74 | RETURN=1 75 | fi 76 | done 77 | 78 | # static analysis 79 | $CPPCHECK $CPPCHECK_OPTS >/dev/null 80 | if [ $? -ne 0 ]; then 81 | RETURN=1 82 | echo "" >&2 83 | echo "Fail to pass static analysis." >&2 84 | echo 85 | fi 86 | 87 | exit $RETURN 88 | -------------------------------------------------------------------------------- /scripts/commit-msg.hook: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # git-good-commit(1) - Git hook to help you write good commit messages. 4 | # Released under the MIT License. 5 | # 6 | # https://github.com/tommarshall/git-good-commit 7 | 8 | COMMIT_MSG_FILE="$1" 9 | COMMIT_MSG_LINES= 10 | HOOK_EDITOR= 11 | SKIP_DISPLAY_WARNINGS=0 12 | WARNINGS= 13 | 14 | RED= 15 | YELLOW= 16 | BLUE= 17 | WHITE= 18 | CYAN= 19 | NC= 20 | 21 | # 22 | # Set colour variables if the output should be coloured. 23 | # 24 | 25 | set_colors() { 26 | local default_color=$(git config --get hooks.goodcommit.color || git config --get color.ui || echo 'auto') 27 | if [[ $default_color == 'always' ]] || [[ $default_color == 'auto' && -t 1 ]]; then 28 | RED='\033[1;31m' 29 | YELLOW='\033[1;33m' 30 | BLUE='\033[1;34m' 31 | WHITE='\033[1;37m' 32 | CYAN='\033[1;36m' 33 | NC='\033[0m' # No Color 34 | fi 35 | } 36 | 37 | # 38 | # Set the hook editor, using the same approach as git. 39 | # 40 | 41 | set_editor() { 42 | # $GIT_EDITOR appears to always be set to `:` when the hook is executed by Git? 43 | # ref: http://stackoverflow.com/q/41468839/885540 44 | # ref: https://github.com/tommarshall/git-good-commit/issues/11 45 | # HOOK_EDITOR=$GIT_EDITOR 46 | test -z "${HOOK_EDITOR}" && HOOK_EDITOR=$(git config --get core.editor) 47 | test -z "${HOOK_EDITOR}" && HOOK_EDITOR=$VISUAL 48 | test -z "${HOOK_EDITOR}" && HOOK_EDITOR=$EDITOR 49 | test -z "${HOOK_EDITOR}" && HOOK_EDITOR='vi' 50 | } 51 | 52 | # 53 | # Output prompt help information. 54 | # 55 | 56 | prompt_help() { 57 | echo -e "${RED}$(cat <<-EOF 58 | e - edit commit message 59 | n - abort commit 60 | ? - print help 61 | EOF 62 | )${NC}" 63 | } 64 | 65 | # 66 | # Add a warning with and . 67 | # 68 | 69 | add_warning() { 70 | local line_number=$1 71 | local warning=$2 72 | WARNINGS[$line_number]="${WARNINGS[$line_number]}$warning;" 73 | } 74 | 75 | # 76 | # Output warnings. 77 | # 78 | 79 | display_warnings() { 80 | if [ $SKIP_DISPLAY_WARNINGS -eq 1 ]; then 81 | # if the warnings were skipped then they should be displayed next time 82 | SKIP_DISPLAY_WARNINGS=0 83 | return 84 | fi 85 | 86 | for i in "${!WARNINGS[@]}"; do 87 | printf "%-74s ${WHITE}%s${NC}\n" "${COMMIT_MSG_LINES[$(($i-1))]}" "[line ${i}]" 88 | IFS=';' read -ra WARNINGS_ARRAY <<< "${WARNINGS[$i]}" 89 | for ERROR in "${WARNINGS_ARRAY[@]}"; do 90 | echo -e " ${YELLOW}- ${ERROR}${NC}" 91 | done 92 | done 93 | 94 | echo 95 | echo -e "${RED}$(cat <<-EOF 96 | How to Write a Git Commit Message: https://chris.beams.io/posts/git-commit/ 97 | EOF 98 | )${NC}" 99 | } 100 | 101 | # 102 | # Read the contents of the commit msg into an array of lines. 103 | # 104 | 105 | read_commit_message() { 106 | # reset commit_msg_lines 107 | COMMIT_MSG_LINES=() 108 | 109 | # read commit message into lines array 110 | while IFS= read -r; do 111 | 112 | # trim trailing spaces from commit lines 113 | shopt -s extglob 114 | REPLY="${REPLY%%*( )}" 115 | shopt -u extglob 116 | 117 | # ignore comments 118 | [[ $REPLY =~ ^# ]] 119 | test $? -eq 0 || COMMIT_MSG_LINES+=("$REPLY") 120 | 121 | done < $COMMIT_MSG_FILE 122 | } 123 | 124 | # 125 | # Validate the contents of the commmit msg agains the good commit guidelines. 126 | # 127 | 128 | validate_commit_message() { 129 | # reset warnings 130 | WARNINGS=() 131 | 132 | # capture the subject, and remove the 'squash! ' prefix if present 133 | COMMIT_SUBJECT=${COMMIT_MSG_LINES[0]/#squash! /} 134 | 135 | # if the commit is empty there's nothing to validate, we can return here 136 | COMMIT_MSG_STR="${COMMIT_MSG_LINES[*]}" 137 | test -z "${COMMIT_MSG_STR[*]// }" && return; 138 | 139 | # if the commit subject starts with 'fixup! ' there's nothing to validate, we can return here 140 | [[ $COMMIT_SUBJECT == 'fixup! '* ]] && return; 141 | 142 | # skip first token in subject (e.g. issue ID from bugtracker which is validated otherwise) 143 | skipfirsttokeninsubject=$(git config --get hooks.goodcommit.subjectskipfirsttoken || echo 'false') 144 | if [ "$skipfirsttokeninsubject" == "true" ]; then 145 | COMMIT_SUBJECT_TO_PROCESS=${COMMIT_SUBJECT#* } 146 | else 147 | COMMIT_SUBJECT_TO_PROCESS=$COMMIT_SUBJECT 148 | fi 149 | 150 | # 0. Check spelling 151 | # ------------------------------------------------------------------------------ 152 | ASPELL=$(which aspell) 153 | if [ $? -ne 0 ]; then 154 | echo "Aspell not installed - unable to check spelling" 155 | else 156 | LINE_NUMBER=1 157 | MISSPELLED_WORDS=`echo "$COMMIT_MSG_LINES[LINE_NUMBER]" | $ASPELL --lang=en --list --home-dir=scripts --personal=aspell-pws` 158 | if [ -n "$MISSPELLED_WORDS" ]; then 159 | add_warning LINE_NUMBER "Possible misspelled word(s): $MISSPELLED_WORDS" 160 | fi 161 | fi 162 | 163 | # 1. Separate subject from body with a blank line 164 | # ------------------------------------------------------------------------------ 165 | 166 | test ${#COMMIT_MSG_LINES[@]} -lt 1 || test -z "${COMMIT_MSG_LINES[1]}" 167 | test $? -eq 0 || add_warning 2 "Separate subject from body with a blank line" 168 | 169 | # 2. Limit the subject line to configured number of characters 170 | # ------------------------------------------------------------------------------ 171 | 172 | subject_max_length=$(git config --get hooks.goodcommit.subjectmaxlength || echo '60') 173 | test "${#COMMIT_SUBJECT}" -le $subject_max_length 174 | test $? -eq 0 || add_warning 1 "Limit the subject line to $subject_max_length characters (${#COMMIT_SUBJECT} chars)" 175 | 176 | # 3. Capitalize the subject line 177 | # ------------------------------------------------------------------------------ 178 | 179 | [[ ${COMMIT_SUBJECT_TO_PROCESS} =~ ^[[:blank:]]*([[:upper:]]{1}[[:lower:]]*|[[:digit:]]+)([[:blank:]]|[[:punct:]]|$) ]] 180 | test $? -eq 0 || add_warning 1 "Capitalize the subject line" 181 | 182 | # 4. Do not end the subject line with a period 183 | # ------------------------------------------------------------------------------ 184 | 185 | [[ ${COMMIT_SUBJECT} =~ [^\.]$ ]] 186 | test $? -eq 0 || add_warning 1 "Do not end the subject line with a period" 187 | 188 | # 5. Use the imperative mood in the subject line 189 | # ------------------------------------------------------------------------------ 190 | 191 | IMPERATIVE_MOOD_BLACKLIST=( 192 | added adds adding 193 | adjusted adjusts adjusting 194 | amended amends amending 195 | avoided avoids avoiding 196 | bumped bumps bumping 197 | changed changes changing 198 | checked checks checking 199 | committed commits committing 200 | copied copies copying 201 | corrected corrects correcting 202 | created creates creating 203 | decreased decreases decreasing 204 | deleted deletes deleting 205 | disabled disables disabling 206 | dropped drops dropping 207 | duplicated duplicates duplicating 208 | enabled enables enabling 209 | excluded excludes excluding 210 | fixed fixes fixing 211 | handled handles handling 212 | implemented implements implementing 213 | improved improves improving 214 | included includes including 215 | increased increases increasing 216 | installed installs installing 217 | introduced introduces introducing 218 | merged merges merging 219 | moved moves moving 220 | pruned prunes pruning 221 | refactored refactors refactoring 222 | released releases releasing 223 | removed removes removing 224 | renamed renames renaming 225 | replaced replaces replacing 226 | resolved resolves resolving 227 | reverted reverts reverting 228 | showed shows showing 229 | tested tests testing 230 | tidied tidies tidying 231 | updated updates updating 232 | used uses using 233 | ) 234 | 235 | # enable case insensitive match 236 | shopt -s nocasematch 237 | 238 | for BLACKLISTED_WORD in "${IMPERATIVE_MOOD_BLACKLIST[@]}"; do 239 | [[ ${COMMIT_SUBJECT_TO_PROCESS} =~ ^[[:blank:]]*$BLACKLISTED_WORD ]] 240 | test $? -eq 0 && add_warning 1 "Use the imperative mood in the subject line, e.g 'fix' not 'fixes'" && break 241 | done 242 | 243 | # disable case insensitive match 244 | shopt -u nocasematch 245 | 246 | # 6. Wrap the body at 72 characters 247 | # ------------------------------------------------------------------------------ 248 | 249 | URL_REGEX='^[[:blank:]]*(https?|ftp|file)://[-A-Za-z0-9\+&@#/%?=~_|!:,.;]*[-A-Za-z0-9\+&@#/%=~_|]' 250 | 251 | for i in "${!COMMIT_MSG_LINES[@]}"; do 252 | LINE_NUMBER=$((i+1)) 253 | test "${#COMMIT_MSG_LINES[$i]}" -le 72 || [[ ${COMMIT_MSG_LINES[$i]} =~ $URL_REGEX ]] 254 | test $? -eq 0 || add_warning $LINE_NUMBER "Wrap the body at 72 characters (${#COMMIT_MSG_LINES[$i]} chars)" 255 | done 256 | 257 | # 7. Use the body to explain what and why vs. how 258 | # ------------------------------------------------------------------------------ 259 | 260 | # ? 261 | 262 | # 8. Do no write single worded commits 263 | # ------------------------------------------------------------------------------ 264 | 265 | COMMIT_SUBJECT_WORDS=(${COMMIT_SUBJECT_TO_PROCESS}) 266 | test "${#COMMIT_SUBJECT_WORDS[@]}" -gt 1 267 | test $? -eq 0 || add_warning 1 "Do no write single worded commits" 268 | 269 | # 9. Do not start the subject line with whitespace 270 | # ------------------------------------------------------------------------------ 271 | 272 | [[ ${COMMIT_SUBJECT_TO_PROCESS} =~ ^[[:blank:]]+ ]] 273 | test $? -eq 1 || add_warning 1 "Do not start the subject line with whitespace" 274 | } 275 | 276 | # 277 | # It's showtime. 278 | # 279 | 280 | set_colors 281 | 282 | set_editor 283 | 284 | if tty >/dev/null 2>&1; then 285 | TTY=$(tty) 286 | else 287 | TTY=/dev/tty 288 | fi 289 | 290 | while true; do 291 | 292 | read_commit_message 293 | 294 | validate_commit_message 295 | 296 | # if there are no WARNINGS are empty then we're good to break out of here 297 | test ${#WARNINGS[@]} -eq 0 && exit 0; 298 | 299 | display_warnings 300 | 301 | # Ask the question (not using "read -p" as it uses stderr not stdout) 302 | echo -en "${CYAN}Proceed with commit? [e/n/?] ${NC}" 303 | 304 | # Read the answer 305 | read REPLY < "$TTY" 306 | 307 | # Check if the reply is valid 308 | case "$REPLY" in 309 | E*|e*) $HOOK_EDITOR "$COMMIT_MSG_FILE" < $TTY; continue ;; 310 | N*|n*) exit 1 ;; 311 | *) SKIP_DISPLAY_WARNINGS=1; prompt_help; continue ;; 312 | esac 313 | 314 | done 315 | -------------------------------------------------------------------------------- /simrupt.c: -------------------------------------------------------------------------------- 1 | /* simrupt: A device that simulates interrupts */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | MODULE_LICENSE("Dual MIT/GPL"); 15 | MODULE_AUTHOR("National Cheng Kung University, Taiwan"); 16 | MODULE_DESCRIPTION("A device that simulates interrupts"); 17 | 18 | /* Macro DECLARE_TASKLET_OLD exists for compatibiity. 19 | * See https://lwn.net/Articles/830964/ 20 | */ 21 | #ifndef DECLARE_TASKLET_OLD 22 | #define DECLARE_TASKLET_OLD(arg1, arg2) DECLARE_TASKLET(arg1, arg2, 0L) 23 | #endif 24 | 25 | #define DEV_NAME "simrupt" 26 | 27 | #define NR_SIMRUPT 1 28 | 29 | static int delay = 100; /* time (in ms) to generate an event */ 30 | 31 | /* Data produced by the simulated device */ 32 | static int simrupt_data; 33 | 34 | /* Timer to simulate a periodic IRQ */ 35 | static struct timer_list timer; 36 | 37 | /* Character device stuff */ 38 | static int major; 39 | static struct class *simrupt_class; 40 | static struct cdev simrupt_cdev; 41 | 42 | /* Data are stored into a kfifo buffer before passing them to the userspace */ 43 | static DECLARE_KFIFO_PTR(rx_fifo, unsigned char); 44 | 45 | /* NOTE: the usage of kfifo is safe (no need for extra locking), until there is 46 | * only one concurrent reader and one concurrent writer. Writes are serialized 47 | * from the interrupt context, readers are serialized using this mutex. 48 | */ 49 | static DEFINE_MUTEX(read_lock); 50 | 51 | /* Wait queue to implement blocking I/O from userspace */ 52 | static DECLARE_WAIT_QUEUE_HEAD(rx_wait); 53 | 54 | /* Generate new data from the simulated device */ 55 | static inline int update_simrupt_data(void) 56 | { 57 | simrupt_data = max((simrupt_data + 1) % 0x7f, 0x20); 58 | return simrupt_data; 59 | } 60 | 61 | /* Insert a value into the kfifo buffer */ 62 | static void produce_data(unsigned char val) 63 | { 64 | /* Implement overwrite-on-full FIFO behavior: 65 | * If the kfifo buffer is full, remove the oldest element 66 | * before inserting the new one. 67 | */ 68 | unsigned int len; 69 | if (kfifo_is_full(&rx_fifo)) { 70 | unsigned char dummy; 71 | len = kfifo_out(&rx_fifo, &dummy, sizeof(dummy)); 72 | if (unlikely(len != sizeof(dummy))) 73 | pr_warn_ratelimited( 74 | "Failed to remove the oldest element (%u bytes)\n", len); 75 | } 76 | len = kfifo_in(&rx_fifo, &val, sizeof(val)); 77 | if (unlikely(len < sizeof(val))) 78 | pr_warn_ratelimited("%s: %zu bytes dropped\n", __func__, 79 | sizeof(val) - len); 80 | 81 | pr_debug("simrupt: %s: in %u/%u bytes\n", __func__, len, 82 | kfifo_len(&rx_fifo)); 83 | } 84 | 85 | /* Mutex to serialize kfifo writers within the workqueue handler */ 86 | static DEFINE_MUTEX(producer_lock); 87 | 88 | /* Mutex to serialize fast_buf consumers: we can use a mutex because consumers 89 | * run in workqueue handler (kernel thread context). 90 | */ 91 | static DEFINE_MUTEX(consumer_lock); 92 | 93 | /* We use an additional "faster" circular buffer to quickly store data from 94 | * interrupt context, before adding them to the kfifo. 95 | */ 96 | static struct circ_buf fast_buf; 97 | 98 | static int fast_buf_get(void) 99 | { 100 | struct circ_buf *ring = &fast_buf; 101 | 102 | /* prevent the compiler from merging or refetching accesses for tail */ 103 | unsigned long head = READ_ONCE(ring->head), tail = ring->tail; 104 | int ret; 105 | 106 | if (unlikely(!CIRC_CNT(head, tail, PAGE_SIZE))) 107 | return -ENOENT; 108 | 109 | /* read index before reading contents at that index */ 110 | smp_rmb(); 111 | 112 | /* extract item from the buffer */ 113 | ret = ring->buf[tail]; 114 | 115 | /* finish reading descriptor before incrementing tail */ 116 | smp_mb(); 117 | 118 | /* increment the tail pointer */ 119 | ring->tail = (tail + 1) & (PAGE_SIZE - 1); 120 | 121 | return ret; 122 | } 123 | 124 | static int fast_buf_put(unsigned char val) 125 | { 126 | struct circ_buf *ring = &fast_buf; 127 | unsigned long head = ring->head; 128 | 129 | /* prevent the compiler from merging or refetching accesses for tail */ 130 | unsigned long tail = READ_ONCE(ring->tail); 131 | 132 | /* is circular buffer full? */ 133 | if (unlikely(!CIRC_SPACE(head, tail, PAGE_SIZE))) 134 | return -ENOMEM; 135 | 136 | ring->buf[ring->head] = val; 137 | 138 | /* commit the item before incrementing the head */ 139 | smp_wmb(); 140 | 141 | /* update header pointer */ 142 | ring->head = (ring->head + 1) & (PAGE_SIZE - 1); 143 | 144 | return 0; 145 | } 146 | 147 | /* Clear all data from the circular buffer fast_buf */ 148 | static void fast_buf_clear(void) 149 | { 150 | fast_buf.head = fast_buf.tail = 0; 151 | } 152 | 153 | /* Workqueue handler: executed by a kernel thread */ 154 | static void simrupt_work_func(struct work_struct *w) 155 | { 156 | int val, cpu; 157 | 158 | /* This code runs from a kernel thread, so softirqs and hard-irqs must 159 | * be enabled. 160 | */ 161 | WARN_ON_ONCE(in_softirq()); 162 | WARN_ON_ONCE(in_interrupt()); 163 | 164 | /* Pretend to simulate access to per-CPU data, disabling preemption 165 | * during the pr_info(). 166 | */ 167 | cpu = get_cpu(); 168 | pr_info("simrupt: [CPU#%d] %s\n", cpu, __func__); 169 | put_cpu(); 170 | 171 | while (1) { 172 | /* Consume data from the circular buffer */ 173 | mutex_lock(&consumer_lock); 174 | val = fast_buf_get(); 175 | mutex_unlock(&consumer_lock); 176 | 177 | if (val < 0) 178 | break; 179 | 180 | /* Store data to the kfifo buffer */ 181 | mutex_lock(&producer_lock); 182 | produce_data(val); 183 | mutex_unlock(&producer_lock); 184 | } 185 | wake_up_interruptible(&rx_wait); 186 | } 187 | 188 | /* Workqueue for asynchronous bottom-half processing */ 189 | static struct workqueue_struct *simrupt_workqueue; 190 | 191 | /* Work item: holds a pointer to the function that is going to be executed 192 | * asynchronously. 193 | */ 194 | static DECLARE_WORK(work, simrupt_work_func); 195 | 196 | /* Tasklet handler. 197 | * 198 | * NOTE: different tasklets can run concurrently on different processors, but 199 | * two of the same type of tasklet cannot run simultaneously. Moreover, a 200 | * tasklet always runs on the same CPU that schedules it. 201 | */ 202 | static void simrupt_tasklet_func(unsigned long __data) 203 | { 204 | ktime_t tv_start, tv_end; 205 | s64 nsecs; 206 | 207 | WARN_ON_ONCE(!in_interrupt()); 208 | WARN_ON_ONCE(!in_softirq()); 209 | 210 | tv_start = ktime_get(); 211 | queue_work(simrupt_workqueue, &work); 212 | tv_end = ktime_get(); 213 | 214 | nsecs = (s64) ktime_to_ns(ktime_sub(tv_end, tv_start)); 215 | 216 | pr_info("simrupt: [CPU#%d] %s in_softirq: %llu usec\n", smp_processor_id(), 217 | __func__, (unsigned long long) nsecs >> 10); 218 | } 219 | 220 | /* Tasklet for asynchronous bottom-half processing in softirq context */ 221 | static DECLARE_TASKLET_OLD(simrupt_tasklet, simrupt_tasklet_func); 222 | 223 | static void process_data(void) 224 | { 225 | WARN_ON_ONCE(!irqs_disabled()); 226 | 227 | pr_info("simrupt: [CPU#%d] produce data\n", smp_processor_id()); 228 | int ret = fast_buf_put(update_simrupt_data()); 229 | if (unlikely(ret < 0)) 230 | pr_warn_ratelimited("simrupt: fast_buf is full, dropping data\n"); 231 | 232 | pr_info("simrupt: [CPU#%d] scheduling tasklet\n", smp_processor_id()); 233 | tasklet_schedule(&simrupt_tasklet); 234 | } 235 | 236 | static void timer_handler(struct timer_list *__timer) 237 | { 238 | ktime_t tv_start, tv_end; 239 | s64 nsecs; 240 | 241 | pr_info("simrupt: [CPU#%d] enter %s\n", smp_processor_id(), __func__); 242 | /* We are using a kernel timer to simulate a hard-irq, so we must expect 243 | * to be in softirq context here. 244 | */ 245 | WARN_ON_ONCE(!in_softirq()); 246 | 247 | /* Disable interrupts for this CPU to simulate real interrupt context */ 248 | local_irq_disable(); 249 | 250 | tv_start = ktime_get(); 251 | process_data(); 252 | tv_end = ktime_get(); 253 | 254 | nsecs = (s64) ktime_to_ns(ktime_sub(tv_end, tv_start)); 255 | 256 | pr_info("simrupt: [CPU#%d] %s in_irq: %llu usec\n", smp_processor_id(), 257 | __func__, (unsigned long long) nsecs >> 10); 258 | mod_timer(&timer, jiffies + msecs_to_jiffies(delay)); 259 | 260 | local_irq_enable(); 261 | } 262 | 263 | static ssize_t simrupt_read(struct file *file, 264 | char __user *buf, 265 | size_t count, 266 | loff_t *ppos) 267 | { 268 | unsigned int read; 269 | int ret; 270 | 271 | pr_debug("simrupt: %s(%p, %zd, %lld)\n", __func__, buf, count, *ppos); 272 | 273 | if (unlikely(!access_ok(buf, count))) 274 | return -EFAULT; 275 | 276 | if (mutex_lock_interruptible(&read_lock)) 277 | return -ERESTARTSYS; 278 | 279 | do { 280 | ret = kfifo_to_user(&rx_fifo, buf, count, &read); 281 | if (unlikely(ret < 0)) 282 | break; 283 | if (read) 284 | break; 285 | if (file->f_flags & O_NONBLOCK) { 286 | ret = -EAGAIN; 287 | break; 288 | } 289 | ret = wait_event_interruptible(rx_wait, kfifo_len(&rx_fifo)); 290 | } while (ret == 0); 291 | pr_debug("simrupt: %s: out %u/%u bytes\n", __func__, read, 292 | kfifo_len(&rx_fifo)); 293 | 294 | mutex_unlock(&read_lock); 295 | 296 | return ret ? ret : read; 297 | } 298 | 299 | static atomic_t open_cnt; 300 | 301 | static int simrupt_open(struct inode *inode, struct file *filp) 302 | { 303 | pr_debug("simrupt: %s\n", __func__); 304 | if (atomic_inc_return(&open_cnt) == 1) 305 | mod_timer(&timer, jiffies + msecs_to_jiffies(delay)); 306 | pr_info("open current cnt: %d\n", atomic_read(&open_cnt)); 307 | 308 | return 0; 309 | } 310 | 311 | static int simrupt_release(struct inode *inode, struct file *filp) 312 | { 313 | pr_debug("simrupt: %s\n", __func__); 314 | if (atomic_dec_and_test(&open_cnt)) { 315 | del_timer_sync(&timer); 316 | flush_workqueue(simrupt_workqueue); 317 | fast_buf_clear(); 318 | } 319 | pr_info("release, current cnt: %d\n", atomic_read(&open_cnt)); 320 | 321 | return 0; 322 | } 323 | 324 | static const struct file_operations simrupt_fops = { 325 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0) 326 | .owner = THIS_MODULE, 327 | #endif 328 | .read = simrupt_read, 329 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 12, 0) 330 | .llseek = no_llseek, 331 | #endif 332 | .open = simrupt_open, 333 | .release = simrupt_release, 334 | }; 335 | 336 | static int __init simrupt_init(void) 337 | { 338 | dev_t dev_id; 339 | struct device *device; 340 | int ret; 341 | 342 | if (kfifo_alloc(&rx_fifo, PAGE_SIZE, GFP_KERNEL) < 0) 343 | return -ENOMEM; 344 | 345 | /* Register major/minor numbers */ 346 | ret = alloc_chrdev_region(&dev_id, 0, NR_SIMRUPT, DEV_NAME); 347 | if (ret) 348 | goto error_alloc; 349 | major = MAJOR(dev_id); 350 | 351 | /* Add the character device to the system */ 352 | cdev_init(&simrupt_cdev, &simrupt_fops); 353 | ret = cdev_add(&simrupt_cdev, dev_id, NR_SIMRUPT); 354 | if (ret) { 355 | kobject_put(&simrupt_cdev.kobj); 356 | goto error_region; 357 | } 358 | 359 | /* Create a class structure */ 360 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0) 361 | simrupt_class = class_create(THIS_MODULE, DEV_NAME); 362 | #else 363 | simrupt_class = class_create(DEV_NAME); 364 | #endif 365 | if (IS_ERR(simrupt_class)) { 366 | printk(KERN_ERR "error creating simrupt class\n"); 367 | ret = PTR_ERR(simrupt_class); 368 | goto error_cdev; 369 | } 370 | 371 | /* Register the device with sysfs */ 372 | device = device_create(simrupt_class, NULL, dev_id, NULL, DEV_NAME); 373 | if (IS_ERR(device)) { 374 | printk(KERN_ERR "Failed to create device.\n"); 375 | ret = PTR_ERR(device); 376 | goto error_device; 377 | } 378 | 379 | /* Allocate fast circular buffer */ 380 | fast_buf.buf = vmalloc(PAGE_SIZE); 381 | if (!fast_buf.buf) { 382 | ret = -ENOMEM; 383 | goto error_vmalloc; 384 | } 385 | 386 | /* Create the workqueue */ 387 | simrupt_workqueue = alloc_workqueue("simruptd", WQ_UNBOUND, WQ_MAX_ACTIVE); 388 | if (!simrupt_workqueue) { 389 | ret = -ENOMEM; 390 | goto error_workqueue; 391 | } 392 | 393 | /* Setup the timer */ 394 | timer_setup(&timer, timer_handler, 0); 395 | atomic_set(&open_cnt, 0); 396 | 397 | pr_info("simrupt: registered new simrupt device: %d,%d\n", major, 0); 398 | out: 399 | return ret; 400 | error_workqueue: 401 | vfree(fast_buf.buf); 402 | error_vmalloc: 403 | device_destroy(simrupt_class, dev_id); 404 | error_device: 405 | class_destroy(simrupt_class); 406 | error_cdev: 407 | cdev_del(&simrupt_cdev); 408 | error_region: 409 | unregister_chrdev_region(dev_id, NR_SIMRUPT); 410 | error_alloc: 411 | kfifo_free(&rx_fifo); 412 | goto out; 413 | } 414 | 415 | static void __exit simrupt_exit(void) 416 | { 417 | dev_t dev_id = MKDEV(major, 0); 418 | 419 | del_timer_sync(&timer); 420 | tasklet_kill(&simrupt_tasklet); 421 | flush_workqueue(simrupt_workqueue); 422 | destroy_workqueue(simrupt_workqueue); 423 | vfree(fast_buf.buf); 424 | device_destroy(simrupt_class, dev_id); 425 | class_destroy(simrupt_class); 426 | cdev_del(&simrupt_cdev); 427 | unregister_chrdev_region(dev_id, NR_SIMRUPT); 428 | 429 | kfifo_free(&rx_fifo); 430 | pr_info("simrupt: unloaded\n"); 431 | } 432 | 433 | module_init(simrupt_init); 434 | module_exit(simrupt_exit); 435 | --------------------------------------------------------------------------------