├── .gitignore ├── .gitmodules ├── Makefile ├── README.md ├── common.h ├── dio.c ├── kthread_sample.c ├── ldt-test ├── ldt.c ├── ldt_plat_dev.c ├── ldt_plat_drv.c ├── ldt_plat_test ├── misc_loop_drv.c ├── misc_loop_drv_test ├── pci-ldt.c └── tracing.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | *.a 4 | all.txt 5 | trace 6 | *.ko 7 | dio 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib"] 2 | path = lib 3 | url = git@github.com:makelinux/lib.git 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #ccflags-y+=-Wfatal-errors 2 | ccflags-y+=-DDEBUG 3 | ccflags-y+=-DUSE_PLATFORM_DEVICE 4 | ccflags-y+=-fmax-errors=5 5 | #ccflags-y+=-D USE_MISCDEV # uncomment to use single misc device instead char devices region 6 | 7 | obj-m+= misc_loop_drv.o 8 | obj-m+= ldt.o 9 | obj-m+= ldt_plat_drv.o # implements platform_driver only 10 | obj-m+= ldt_plat_dev.o # implements platform_device and resource 11 | #obj-m+= chrdev_region_sample.o 12 | obj-m+= kthread_sample.o 13 | obj-m+= pci-ldt.o 14 | 15 | export CPATH:=${PWD}/lib 16 | 17 | KERNELDIR ?= /lib/modules/$(shell uname -r)/build 18 | 19 | all: modules dio 20 | 21 | check: 22 | ./ldt-test 23 | 24 | 25 | modules: 26 | $(MAKE) -C $(KERNELDIR) M=$$PWD modules 27 | 28 | modules_install: 29 | $(MAKE) -C $(KERNELDIR) M=$$PWD modules_install 30 | 31 | clean: 32 | rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c *.mod \ 33 | .tmp_versions modules.order Module.symvers .cache.mk \ 34 | dio *.tmp *.log 35 | 36 | dio: CPPFLAGS+= -DCTRACER_ON -include ctracer.h -g 37 | #dio: CPPFLAGS+= -D VERBOSE 38 | 39 | #_src = dio.c ldt.c ldt_plat_dev.c ldt_plat_drv.c ctracer.h ldt_configfs_basic.c ctracer.h tracing.h 40 | _src = dio.c ldt.c ldt_plat_dev.c ldt_plat_drv.c ctracer.h ldt_configfs_basic.c misc_loop_drv.c 41 | _src+= pci-ldt.c 42 | 43 | checkpatch: 44 | checkpatch.pl --no-tree --show-types --ignore LINE_CONTINUATIONS --terse -f $(_src) Makefile 45 | 46 | checkpatch2: 47 | checkpatch.pl --no-tree --show-types --ignore LONG_LINE,LINE_CONTINUATIONS --terse -f $(_src) Makefile 48 | 49 | astyle: 50 | astyle --style=linux --lineend=linux --indent=force-tab=8 \ 51 | --pad-comma --pad-header --pad-oper --keep-one-line-blocks --unpad-paren \ 52 | --attach-inlines --indent-labels --max-continuation-indent=80 $(_src) 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LDT - Linux Driver Template 2 | 3 | LDT project is useful for Linux driver development beginners and as starting point for a new drivers. 4 | The driver uses following Linux facilities: 5 | module, platform driver, file operations (read/write, mmap, ioctl, blocking and nonblocking mode, polling), kfifo, completion, interrupt, tasklet, work, kthread, timer, simple misc device, multiple char devices, Device Model, configfs, UART 0x3f8, HW loopback, SW loopback, ftracer. 6 | 7 | ## Usage: 8 | 9 | Just run 10 | 11 | git clone --recurse-submodules git://github.com/makelinux/ldt.git && cd ldt && make && ./ldt-test && sudo ./misc_loop_drv_test 12 | 13 | and explore sources. 14 | 15 | ## Files: 16 | 17 | Main source file of LDT: 18 | **[ldt.c](https://github.com/makelinux/ldt/blob/master/ldt.c)** 19 | 20 | Test script, run it: **[ldt-test](https://github.com/makelinux/ldt/blob/master/ldt-test)** 21 | 22 | Generic testing utility for Device I/O: **[dio.c](https://github.com/makelinux/ldt/blob/master/dio.c)** 23 | 24 | Simple misc driver with read, write, fifo, tasklet and IRQ: 25 | **[misc_loop_drv.c](https://github.com/makelinux/ldt/blob/master/misc_loop_drv.c)** 26 | 27 | Browse the rest of source: https://github.com/makelinux/ldt/ 28 | -------------------------------------------------------------------------------- /common.h: -------------------------------------------------------------------------------- 1 | #ifndef __LDT_COMMON_H__ 2 | #define __LDT_COMMON_H__ 3 | 4 | #include 5 | #include 6 | 7 | #if LINUX_VERSION_CODE < KERNEL_VERSION(4,2,0) 8 | 9 | #include 10 | 11 | #ifndef ISA_IRQ_VECTOR 12 | #define ISA_IRQ_VECTOR(irq) (((FIRST_EXTERNAL_VECTOR + 16) & ~15) + irq) 13 | #endif 14 | 15 | #endif 16 | 17 | /* With Linux 3.8 the __devinit macro and others were removed from 18 | Defining them as empty here to keep the other code portable to older kernel versions. */ 19 | 20 | #include 21 | 22 | #ifndef __devinit 23 | 24 | #define __devinit 25 | #define __devinitdata 26 | #define __devinitconst 27 | #define __devexit 28 | #define __devexitdata 29 | #define __devexitconst 30 | 31 | #if defined(MODULE) || defined(CONFIG_HOTPLUG) 32 | #define __devexit_p(x) x 33 | #else 34 | #define __devexit_p(x) NULL 35 | #endif 36 | 37 | #endif 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /dio.c: -------------------------------------------------------------------------------- 1 | /* 2 | * DIO - Device Input/Output utility for testing device drivers 3 | * 4 | * stdin/stdout <--> dio <--> mmap, ioctl, read/write 5 | * 6 | * Copyright (C) 2012 Constantine Shulyupin 7 | * http://www.makelinux.net/ 8 | * 9 | * Dual BSD/GPL License 10 | * 11 | */ 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include "ctracer.h" 35 | 36 | static enum io_type { 37 | file_io, 38 | mmap_io, 39 | ioctl_io 40 | } io_type; 41 | 42 | static void *inbuf, *outbuf; 43 | static void *mm; 44 | static void *mem; 45 | static int buf_size; 46 | static int offset; 47 | static char *dev_name; 48 | static int ignore_eof; 49 | static int ioctl_num; 50 | static int loops; 51 | static int delay; 52 | static char ioctl_type = 'A'; 53 | __thread int ret; 54 | __thread int ctracer_ret; 55 | static int ro, wo; /* read only, write only*/ 56 | 57 | /* 58 | #define VERBOSE 59 | */ 60 | 61 | int output(int dev, void *buf, int size) 62 | { 63 | #ifdef VERBOSE 64 | _entry: 65 | trl_(); 66 | trvd(size); 67 | #endif 68 | ret = 0; 69 | if (dev < 0 || ro) 70 | return 0; 71 | switch (io_type) { 72 | case mmap_io: 73 | memcpy(mem, buf, size); 74 | ret = size; 75 | break; 76 | case ioctl_io: 77 | ioctl(dev, _IOC(_IOC_WRITE, ioctl_type, ioctl_num, 78 | size & _IOC_SIZEMASK), buf); 79 | break; 80 | case file_io: 81 | default: 82 | ret = write(dev, buf, size); 83 | } 84 | return ret; 85 | } 86 | 87 | int input(int dev, void *buf, int size) 88 | { 89 | ret = 0; 90 | #ifdef VERBOSE 91 | _entry: 92 | trl_(); 93 | trvd(size); 94 | #endif 95 | if (dev < 0 || wo) 96 | return 0; 97 | switch (io_type) { 98 | case mmap_io: 99 | memcpy(buf, mem, size); 100 | ret = size; 101 | break; 102 | case ioctl_io: 103 | ioctl(dev, _IOC(_IOC_READ, ioctl_type, ioctl_num, 104 | size & _IOC_SIZEMASK), buf); 105 | ret = size; 106 | break; 107 | case file_io: 108 | default: 109 | ret = read(dev, buf, size); 110 | } 111 | return ret; 112 | } 113 | 114 | int io_start(int dev) 115 | { 116 | struct pollfd pfd[2]; 117 | ssize_t data_in_len, data_out_len, len_total = 0; 118 | int i = 0; 119 | 120 | /* TODO: wo, ro */ 121 | pfd[0].fd = fileno(stdin); 122 | pfd[0].events = POLLIN; 123 | pfd[1].fd = dev; 124 | pfd[1].events = POLLIN; 125 | while (poll(pfd, sizeof(pfd) / sizeof(pfd[0]), -1) > 0) { 126 | #ifdef VERBOSE 127 | trvd_(i); 128 | trvx_(pfd[0].revents); 129 | trvx_(pfd[1].revents); 130 | trln(); 131 | #endif 132 | data_in_len = 0; 133 | if (pfd[0].revents & POLLIN) { 134 | pfd[0].revents = 0; 135 | ret = data_in_len = read(fileno(stdin), inbuf, buf_size); 136 | if (data_in_len < 0) { 137 | usleep(100000); 138 | break; 139 | } 140 | if (!data_in_len && !ignore_eof) { 141 | /* read returns 0 on End Of File */ 142 | break; 143 | } 144 | #ifdef VERBOSE 145 | trvd_(data_in_len); 146 | trln(); 147 | #endif 148 | again: 149 | chkne(ret = output(dev, inbuf, data_in_len)); 150 | if (ret < 0 && errno == EAGAIN) { 151 | usleep(100000); 152 | goto again; 153 | } 154 | if (data_in_len > 0) 155 | len_total += data_in_len; 156 | } 157 | data_out_len = 0; 158 | if (pfd[1].revents & POLLIN) { 159 | pfd[1].revents = 0; 160 | chkne(ret = data_out_len = input(dev, outbuf, buf_size)); 161 | if (data_out_len < 0) { 162 | usleep(100000); 163 | break; 164 | } 165 | if (!data_out_len) { 166 | /* EOF, don't expect data from the file any more 167 | but wee can continue to write */ 168 | pfd[1].events = 0; 169 | } 170 | if (!data_out_len && !ignore_eof) { 171 | /* read returns 0 on End Of File */ 172 | break; 173 | } 174 | write(fileno(stdout), outbuf, data_out_len); 175 | if (data_out_len > 0) 176 | len_total += data_out_len; 177 | } 178 | #ifdef VERBOSE 179 | trl_(); 180 | trvd_(i); 181 | trvd_(len_total); 182 | trvd_(data_in_len); 183 | trvd_(data_out_len); 184 | trln(); 185 | #endif 186 | if ((!ignore_eof && pfd[0].revents & POLLHUP) || pfd[1].revents & POLLHUP) 187 | break; 188 | i++; 189 | if (loops && i >= loops) 190 | break; 191 | usleep(1000 * delay); 192 | } 193 | #ifdef VERBOSE 194 | trl_(); 195 | trvd_(i); 196 | trvd_(len_total); 197 | trvd_(data_in_len); 198 | trvd_(data_out_len); 199 | trln(); 200 | #endif 201 | return ret; 202 | } 203 | 204 | #define add_literal_option(o) do { options[optnum].name = #o; \ 205 | options[optnum].flag = (void *)&o; options[optnum].has_arg = 1; \ 206 | options[optnum].val = -1; optnum++; } while (0) 207 | 208 | #define add_flag_option(n, p, v) do { options[optnum].name = n; \ 209 | options[optnum].flag = (void *)p; options[optnum].has_arg = 0; \ 210 | options[optnum].val = v; optnum++; } while (0) 211 | 212 | static struct option options[100]; 213 | int optnum; 214 | static int verbose; 215 | 216 | int options_init() 217 | { 218 | optnum = 0; 219 | /* on gcc 64, pointer to variable can be used only on run-time 220 | */ 221 | memset(options, 0, sizeof(options)); 222 | add_literal_option(io_type); 223 | add_literal_option(buf_size); 224 | add_literal_option(ioctl_num); 225 | add_literal_option(ioctl_type); 226 | add_literal_option(loops); 227 | add_literal_option(delay); 228 | add_literal_option(offset); 229 | add_flag_option("ioctl", &io_type, ioctl_io); 230 | add_flag_option("mmap", &io_type, mmap_io); 231 | add_flag_option("file", &io_type, file_io); 232 | add_flag_option("ignore_eof", &ignore_eof, 1); 233 | add_flag_option("verbose", &verbose, 1); 234 | add_flag_option("ro", &ro, 1); 235 | add_flag_option("wo", &wo, 1); 236 | options[optnum].name = strdup("help"); 237 | options[optnum].has_arg = 0; 238 | options[optnum].val = 'h'; 239 | optnum++; 240 | return optnum; 241 | } 242 | 243 | /* 244 | * expand_arg, return_if_arg_is_equal - utility functions 245 | * to translate command line parameters 246 | * from string to numeric values using predefined preprocessor defines 247 | */ 248 | 249 | #define return_if_arg_is_equal(entry) do { if (0 == strcmp(arg, #entry)) return entry; } while (0) 250 | 251 | int expand_arg(char *arg) 252 | { 253 | if (!arg) 254 | return 0; 255 | /* 256 | return_if_arg_is_equal(SOCK_STREAM); 257 | */ 258 | return strtol(arg, NULL, 0); 259 | } 260 | 261 | char *usage = "dio - Device Input/Output utility\n\ 262 | Usage:\n\ 263 | dio \n\ 264 | \n\ 265 | options:\n\ 266 | \n\ 267 | default values are marked with '*'\n\ 268 | \n\ 269 | -h | --help\n\ 270 | show this help\n\ 271 | \n\ 272 | --buf_size \n\ 273 | I/O buffer size\n\ 274 | \n\ 275 | Samples:\n\ 276 | \n\ 277 | TBD\n\ 278 | \n"; 279 | 280 | int init(int argc, char *argv[]) 281 | { 282 | int opt = 0; 283 | int longindex = 0; 284 | options_init(); 285 | opterr = 0; 286 | while ((opt = getopt_long(argc, argv, "h", options, &longindex)) != -1) { 287 | switch (opt) { 288 | case 0: 289 | if (options[longindex].val == -1) 290 | *options[longindex].flag = expand_arg(optarg); 291 | break; 292 | case 'h': 293 | printf("%s", usage); 294 | exit(0); 295 | break; 296 | default: /* '?' */ 297 | printf("Error in arguments\n"); 298 | trvx(opt); 299 | exit(EXIT_FAILURE); 300 | } 301 | } 302 | if (optind < argc) 303 | dev_name = argv[optind]; 304 | if (io_type == ioctl_io && buf_size >= 1 << _IOC_SIZEBITS) 305 | fprintf(stderr, "WARNING: size of ioctl data it too big\n"); 306 | return 0; 307 | } 308 | 309 | int main(int argc, char *argv[]) 310 | { 311 | int dev; 312 | 313 | buf_size = sysconf(_SC_PAGESIZE); 314 | init(argc, argv); 315 | verbose && fprintf(stderr, "%s compiled " __DATE__ " " __TIME__ "\n", argv[0]); 316 | if (io_type == ioctl_io && buf_size >= 1 << _IOC_SIZEBITS) 317 | buf_size = (1 << _IOC_SIZEBITS) - 1; 318 | inbuf = malloc(buf_size); 319 | outbuf = malloc(buf_size); 320 | chkne(dev = open(dev_name, O_CREAT | O_RDWR, 0666)); 321 | if (io_type == mmap_io) { 322 | mm = mmap(NULL, buf_size, PROT_READ | PROT_WRITE, 323 | MAP_SHARED, dev, offset & ~(sysconf(_SC_PAGESIZE) - 1)); 324 | if (mm == MAP_FAILED) { 325 | warn("mmap() failed"); 326 | goto exit; 327 | } 328 | mem = mm + (offset & (sysconf(_SC_PAGESIZE) - 1)); 329 | } 330 | if (verbose) { 331 | trvs_(dev_name); 332 | trvd_(io_type); 333 | trvd_(buf_size); 334 | trvd_(ignore_eof); 335 | trvd_(verbose); 336 | trvp_(mm); 337 | trvp_(mem); 338 | trln(); 339 | } 340 | switch (io_type) { 341 | case mmap_io: 342 | case ioctl_io: 343 | if (!ro) { 344 | chkne(ret = read(fileno(stdin), inbuf, buf_size)); 345 | if (ret < 0) 346 | goto exit; 347 | chkne(ret = output(dev, inbuf, ret)); 348 | } 349 | if (!wo) { 350 | chkne(ret = input(dev, outbuf, buf_size)); 351 | if (ret < 0) 352 | goto exit; 353 | write(fileno(stdout), outbuf, ret); 354 | } 355 | break; 356 | case file_io: 357 | default: 358 | io_start(dev); 359 | } 360 | exit: 361 | if (mm && mm != MAP_FAILED) 362 | munmap(mm, buf_size); 363 | free(outbuf); 364 | free(inbuf); 365 | close(dev); 366 | exit(EXIT_SUCCESS); 367 | } 368 | -------------------------------------------------------------------------------- /kthread_sample.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) 5 | #include 6 | #endif 7 | 8 | static DECLARE_COMPLETION(completion); 9 | 10 | static int thread_sample(void *data) 11 | { 12 | int ret = 0; 13 | allow_signal(SIGINT); 14 | while (!kthread_should_stop()) { 15 | ret = wait_for_completion_interruptible(&completion); 16 | if (ret == -ERESTARTSYS) { 17 | pr_debug("interrupted\n"); 18 | return -EINTR; 19 | } 20 | /* 21 | perform here a useful work in scheduler context 22 | */ 23 | } 24 | return ret; 25 | } 26 | 27 | static struct task_struct *thread; 28 | 29 | static int thread_sample_init(void) 30 | { 31 | int ret = 0; 32 | thread = kthread_run(thread_sample, NULL, "%s", KBUILD_MODNAME); 33 | if (IS_ERR(thread)) { 34 | ret = PTR_ERR(thread); 35 | goto exit; 36 | } 37 | complete(&completion); 38 | exit: 39 | return ret; 40 | } 41 | 42 | static void thread_sample_exit(void) 43 | { 44 | if (!IS_ERR_OR_NULL(thread)) { 45 | send_sig(SIGINT, thread, 1); 46 | kthread_stop(thread); 47 | } 48 | } 49 | 50 | module_init(thread_sample_init); 51 | module_exit(thread_sample_exit); 52 | 53 | MODULE_LICENSE("Dual BSD/GPL"); 54 | -------------------------------------------------------------------------------- /ldt-test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # LDT - Linux Driver Template 5 | # 6 | # Test script for driver LDT 7 | # 8 | # Copyright (C) 2012 Constantine Shulyupin http://www.makelinux.net/ 9 | # 10 | # Licensed under the GPLv2. 11 | # 12 | 13 | RED="\\033[0;31m" 14 | NOCOLOR="\\033[0;39m" 15 | GREEN="\\033[0;32m" 16 | 17 | set -o errtrace 18 | debugfs=`grep debugfs /proc/mounts | awk '{ print $2; }'` 19 | tracing=$debugfs/tracing 20 | 21 | tracing() 22 | { 23 | sudo sh -c "cd $tracing; $1" || true 24 | } 25 | 26 | tracing_start() 27 | { 28 | tracing "echo :mod:ldt > set_ftrace_filter" 29 | tracing "echo function > current_tracer" # need for draw_functrace.py 30 | tracing "echo 1 > function_profile_enabled" 31 | } 32 | 33 | tracing_stop() 34 | { 35 | ( echo Profiling data per CPU 36 | tracing "cat trace_stat/function*" )> trace_stat.log && echo trace_stat.log saved 37 | tracing "echo 0 > function_profile_enabled" 38 | sudo cp $tracing/trace ftrace.log && echo ftrace.log saved 39 | sudo dd iflag=nonblock if=$tracing/trace_pipe 2> /dev/null > trace_pipe.log || true && echo trace_pipe.log saved 40 | tracing "echo nop > current_tracer" 41 | #export PYTHONPATH=/usr/src/linux-headers-$(uname -r)/scripts/tracing/ 42 | # draw_functrace.py needs function tracer 43 | python /usr/src/linux-headers-$(uname -r)/scripts/tracing/draw_functrace.py \ 44 | < trace_pipe.log > functrace.log && echo functrace.log saved || true 45 | } 46 | 47 | sudo dmesg -n 7 48 | sudo rmmod ldt 2> /dev/null 49 | sudo dmesg -c > /dev/null 50 | 51 | # configure UART because it is used for demostration of the driver 52 | stty -F /dev/ttyS0 115200 53 | set -o errexit 54 | 55 | # 56 | # Check for presence looback on /dev/ttyS0. 57 | # If loopback is not present, switch loopback on in the driver 58 | # 59 | 60 | data='loopback?' 61 | received=`echo $data | ./dio --ignore_eof --loops 2 --delay 10 /dev/ttyS0 2> /dev/null` 62 | if [ "$data" == "$received" ]; then 63 | echo -e "Loopback on /dev/ttyS0 detected" 64 | loopback=0 65 | else 66 | echo -e "No loopback behind /dev/ttyS0 detected, running ldt driver with UART in loopback mode" 67 | loopback=1 68 | fi 69 | 70 | # clean data 71 | echo | ./dio --ignore_eof --loops 10 --delay 10 /dev/ttyS0 2> /dev/null > /dev/null 72 | 73 | sudo insmod ldt.ko loopback=$loopback 74 | 75 | tracing_start || true 76 | #. /sys/class/misc/ldt/uevent 2> /dev/null || true # get $MAJOR $MINOR 77 | #. /sys/class/ldt/ldt/uevent 2> /dev/null || true # get $MAJOR $MINOR 78 | #sudo sh -c "rm /dev/ldt;sudo mknod /dev/ldt c $MAJOR $MINOR; chmod o+rw /dev/ldt" 79 | sudo sh -c "chmod go+rw /dev/ldt*" 80 | data=123rw 81 | echo $data > /dev/ldt 82 | sleep 0.5 83 | received=`dd iflag=nonblock if=/dev/ldt 2> /dev/null || true` 84 | if [ "$data" == "$received" ]; then 85 | echo -e "${GREEN}LDT nonblocking read/write test passed$NOCOLOR" 86 | else 87 | echo -e "${RED}LDT nonblock read/write test failed$NOCOLOR" 88 | echo expected $data 89 | echo received $received 90 | fi 91 | 92 | data=123bl 93 | cat /dev/ldt > R.tmp & 94 | sleep 0.5; echo $data > /dev/ldt; 95 | sleep 0.5 96 | kill %1; wait %1 2> /dev/null || true 97 | received=`cat R.tmp` 98 | rm -f R.tmp 99 | 100 | if [ "$data" == "$received" ]; then 101 | echo -e "${GREEN}LDT blocking read/write test passed$NOCOLOR" 102 | else 103 | echo -e "${RED}LDT blocking read/write test failed$NOCOLOR" 104 | echo expected $data 105 | echo received $received 106 | fi 107 | 108 | data=123mmap 109 | received=`sudo echo $data | ./dio --mmap /dev/ldt` 110 | if [ "$data" == "$received" ]; then 111 | echo -e "${GREEN}LDT mmap test passed$NOCOLOR" 112 | else 113 | echo -e "${RED}LDT mmap test failed$NOCOLOR" 114 | echo expected $data 115 | echo received $received 116 | fi 117 | 118 | data=123ioctl 119 | received=`sudo echo $data | ./dio --ioctl /dev/ldt` 120 | if [ "$data" == "$received" ]; then 121 | echo -e "${GREEN}LDT ioctl test passed$NOCOLOR" 122 | else 123 | echo -e "${RED}LDT ioctl test failed$NOCOLOR" 124 | echo expected $data 125 | echo received $received 126 | fi 127 | 128 | sudo ls -l /sys/kernel/debug/ldt 129 | 130 | tracing_stop || true 131 | sudo dmesg --notime --show-delta --read-clear 2>/dev/null > kernel.log || \ 132 | sudo dmesg -c > kernel.log && echo kernel.log saved 133 | -------------------------------------------------------------------------------- /ldt.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LDT - Linux Driver Template 3 | * 4 | * Copyright (C) 2012 Constantine Shulyupin http://www.makelinux.net/ 5 | * 6 | * Licensed under the GPLv2. 7 | * 8 | * 9 | * The driver demonstrates usage of following Linux facilities: 10 | * 11 | * Linux kernel module 12 | * file_operations 13 | * read and write (UART) 14 | * blocking read and write 15 | * polling 16 | * mmap 17 | * ioctl 18 | * kfifo 19 | * completion 20 | * interrupt 21 | * tasklet 22 | * timer 23 | * work 24 | * simple single misc device file (miscdevice, misc_register) 25 | * debugfs 26 | * platform_driver and platform_device in another module 27 | * simple UART driver on port 0x3f8 with IRQ 4 28 | * 29 | * Use test script ldt-test to see the driver running 30 | * 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | 51 | #undef pr_fmt 52 | #define pr_fmt(fmt) "%s:%d: %s " fmt, __FILE__, __LINE__, __func__ 53 | 54 | static int port = 0x3f8; 55 | module_param(port, int, 0); 56 | MODULE_PARM_DESC(port, "io port number, default 0x3f8 - UART"); 57 | 58 | static int port_size = 8; 59 | module_param(port_size, int, 0); 60 | MODULE_PARM_DESC(port_size, "number of io ports, default 8"); 61 | 62 | static int irq = 4; 63 | module_param(irq, int, 0); 64 | MODULE_PARM_DESC(irq, "interrupt request number, default 4 - UART"); 65 | 66 | static int loopback; 67 | module_param(loopback, int, 0); 68 | MODULE_PARM_DESC(loopback, "loopback mode for testing, default 0"); 69 | 70 | #define FIFO_SIZE 128 /* must be power of two */ 71 | 72 | static int bufsize = 8 * PAGE_SIZE; 73 | 74 | /** 75 | * struct ldt_data - the driver data 76 | * @in_buf: input buffer for mmap interface 77 | * @out_buf: outoput buffer for mmap interface 78 | * @in_fifo: input queue for write 79 | * @out_fifo: output queue for read 80 | * @fifo_lock: lock for queues 81 | * @readable: waitqueue for blocking read 82 | * @writeable: waitqueue for blocking write 83 | * @port_ptr: mapped io port 84 | * @uart_detected: UART is detected and will be used. 85 | * Otherwise emulation mode will be used. 86 | * 87 | * stored in static global variable drvdata for simplicity. 88 | * Can be also retrieved from platform_device with 89 | * struct ldt_data *drvdata = platform_get_drvdata(pdev); 90 | */ 91 | 92 | struct ldt_data { 93 | void *in_buf; 94 | void *out_buf; 95 | DECLARE_KFIFO(in_fifo, char, FIFO_SIZE); 96 | DECLARE_KFIFO(out_fifo, char, FIFO_SIZE); 97 | spinlock_t fifo_lock; 98 | wait_queue_head_t readable, writeable; 99 | struct mutex read_lock; 100 | struct mutex write_lock; 101 | void __iomem *port_ptr; 102 | int uart_detected; 103 | }; 104 | 105 | static struct ldt_data *drvdata; 106 | 107 | /** 108 | * ldt_received - puts data to receive queue 109 | * @data: received data 110 | */ 111 | 112 | static void ldt_received(char data) 113 | { 114 | kfifo_in_spinlocked(&drvdata->in_fifo, &data, 115 | sizeof(data), &drvdata->fifo_lock); 116 | wake_up_interruptible(&drvdata->readable); 117 | } 118 | 119 | /** 120 | * ldt_send - sends data to HW port or emulates SW loopback 121 | * @data: data to send 122 | */ 123 | 124 | static void ldt_send(char data) 125 | { 126 | if (drvdata->uart_detected) 127 | iowrite8(data, drvdata->port_ptr + UART_TX); 128 | else if (loopback) 129 | ldt_received(data); 130 | } 131 | 132 | static inline u8 tx_ready(void) 133 | { 134 | return ioread8(drvdata->port_ptr + UART_LSR) & UART_LSR_THRE; 135 | } 136 | 137 | static inline u8 rx_ready(void) 138 | { 139 | return ioread8(drvdata->port_ptr + UART_LSR) & UART_LSR_DR; 140 | } 141 | 142 | /* 143 | * tasklet section 144 | * 145 | * template function for deferred call in interrupt context 146 | */ 147 | 148 | 149 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) 150 | static void ldt_tasklet_func(struct tasklet_struct *t) 151 | #else 152 | static void ldt_tasklet_func(unsigned long d) 153 | #endif 154 | { 155 | char data_out; 156 | 157 | if (drvdata->uart_detected) { 158 | while (tx_ready() && kfifo_out_spinlocked(&drvdata->out_fifo, 159 | &data_out, sizeof(data_out), 160 | &drvdata->fifo_lock)) { 161 | wake_up_interruptible(&drvdata->writeable); 162 | pr_debug("data_out=%d %c\n", data_out, data_out >= 32 ? data_out : ' '); 163 | ldt_send(data_out); 164 | } 165 | while (rx_ready()) { 166 | char data_in; 167 | data_in = ioread8(drvdata->port_ptr + UART_RX); 168 | pr_debug("data_in=%d %c\n", data_in, data_in >= 32 ? data_in : ' '); 169 | ldt_received(data_in); 170 | } 171 | } else { 172 | while (kfifo_out_spinlocked(&drvdata->out_fifo, 173 | &data_out, sizeof(data_out), 174 | &drvdata->fifo_lock)) { 175 | wake_up_interruptible(&drvdata->writeable); 176 | pr_debug("data_out=%d\n", data_out); 177 | ldt_send(data_out); 178 | } 179 | } 180 | } 181 | 182 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) 183 | static DECLARE_TASKLET(ldt_tasklet, ldt_tasklet_func); 184 | #else 185 | static DECLARE_TASKLET(ldt_tasklet, ldt_tasklet_func, 0); 186 | #endif 187 | 188 | /* 189 | * interrupt section 190 | */ 191 | 192 | static int isr_counter; 193 | 194 | static irqreturn_t ldt_isr(int irq, void *dev_id) 195 | { 196 | /* 197 | * UART interrupt is not fired in loopback mode, 198 | * therefore fire ldt_tasklet from timer too 199 | */ 200 | isr_counter++; 201 | pr_debug("UART_FCR=0x%02X\n", ioread8(drvdata->port_ptr + UART_FCR)); 202 | pr_debug("UART_IIR=0x%02X\n", ioread8(drvdata->port_ptr + UART_IIR)); 203 | tasklet_schedule(&ldt_tasklet); 204 | return IRQ_HANDLED; /* our IRQ */ 205 | } 206 | 207 | /* 208 | * timer section 209 | */ 210 | 211 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) 212 | 213 | static void ldt_timer_func(struct timer_list *ldt_timer) 214 | { 215 | /* 216 | * this timer is used just to fire ldt_tasklet, 217 | * because there is no interrupts in loopback mode 218 | */ 219 | if (loopback) 220 | tasklet_schedule(&ldt_tasklet); 221 | mod_timer(ldt_timer, jiffies + HZ / 100); 222 | } 223 | 224 | static DEFINE_TIMER(ldt_timer, ldt_timer_func); 225 | 226 | #else 227 | 228 | static struct timer_list ldt_timer; 229 | 230 | static void ldt_timer_func(unsigned long data) 231 | { 232 | /* 233 | * this timer is used just to fire ldt_tasklet, 234 | * because there is no interrupts in loopback mode 235 | */ 236 | if (loopback) 237 | tasklet_schedule(&ldt_tasklet); 238 | mod_timer(&ldt_timer, jiffies + HZ / 100); 239 | } 240 | 241 | static DEFINE_TIMER(ldt_timer, ldt_timer_func, 0, 0); 242 | 243 | #endif 244 | 245 | /* 246 | * file_operations section 247 | */ 248 | 249 | static int ldt_open(struct inode *inode, struct file *file) 250 | { 251 | pr_debug("from %s\n", current->comm); 252 | /* client related data can be allocated here and 253 | stored in file->private_data */ 254 | return 0; 255 | } 256 | 257 | static int ldt_release(struct inode *inode, struct file *file) 258 | { 259 | pr_debug("from %s\n", current->comm); 260 | /* client related data can be retrived from file->private_data 261 | and released here */ 262 | return 0; 263 | } 264 | 265 | static ssize_t ldt_read(struct file *file, char __user *buf, 266 | size_t count, loff_t *ppos) 267 | { 268 | int ret = 0; 269 | unsigned int copied; 270 | 271 | pr_debug("from %s\n", current->comm); 272 | if (kfifo_is_empty(&drvdata->in_fifo)) { 273 | if (file->f_flags & O_NONBLOCK) { 274 | return -EAGAIN; 275 | } else { 276 | pr_debug("waiting\n"); 277 | ret = wait_event_interruptible(drvdata->readable, 278 | !kfifo_is_empty(&drvdata->in_fifo)); 279 | if (ret == -ERESTARTSYS) { 280 | pr_err("%s\n", "interrupted"); 281 | return -EINTR; 282 | } 283 | } 284 | } 285 | if (mutex_lock_interruptible(&drvdata->read_lock)) 286 | return -EINTR; 287 | ret = kfifo_to_user(&drvdata->in_fifo, buf, count, &copied); 288 | mutex_unlock(&drvdata->read_lock); 289 | return ret ? ret : copied; 290 | } 291 | 292 | static ssize_t ldt_write(struct file *file, const char __user *buf, 293 | size_t count, loff_t *ppos) 294 | { 295 | int ret; 296 | unsigned int copied; 297 | 298 | pr_debug("from %s\n", current->comm); 299 | if (kfifo_is_full(&drvdata->out_fifo)) { 300 | if (file->f_flags & O_NONBLOCK) { 301 | return -EAGAIN; 302 | } else { 303 | ret = wait_event_interruptible(drvdata->writeable, 304 | !kfifo_is_full(&drvdata->out_fifo)); 305 | if (ret == -ERESTARTSYS) { 306 | pr_err("%s\n", "interrupted"); 307 | return -EINTR; 308 | } 309 | } 310 | } 311 | if (mutex_lock_interruptible(&drvdata->write_lock)) 312 | return -EINTR; 313 | ret = kfifo_from_user(&drvdata->out_fifo, buf, count, &copied); 314 | mutex_unlock(&drvdata->write_lock); 315 | tasklet_schedule(&ldt_tasklet); 316 | return ret ? ret : copied; 317 | } 318 | 319 | static unsigned int ldt_poll(struct file *file, poll_table *pt) 320 | { 321 | unsigned int mask = 0; 322 | poll_wait(file, &drvdata->readable, pt); 323 | poll_wait(file, &drvdata->writeable, pt); 324 | 325 | if (!kfifo_is_empty(&drvdata->in_fifo)) 326 | mask |= POLLIN | POLLRDNORM; 327 | mask |= POLLOUT | POLLWRNORM; 328 | /* 329 | if case of output end of file set 330 | mask |= POLLHUP; 331 | in case of output error set 332 | mask |= POLLERR; 333 | */ 334 | return mask; 335 | } 336 | 337 | /* 338 | * pages_flag - set or clear a flag for sequence of pages 339 | * 340 | * more generic solution instead SetPageReserved, ClearPageReserved etc 341 | * 342 | * Poposing to move pages_flag to linux/page-flags.h 343 | */ 344 | 345 | static void pages_flag(struct page *page, int page_num, int mask, int value) 346 | { 347 | for (; page_num; page_num--, page++) 348 | if (value) 349 | __set_bit(mask, &page->flags); 350 | else 351 | __clear_bit(mask, &page->flags); 352 | } 353 | 354 | static int ldt_mmap(struct file *filp, struct vm_area_struct *vma) 355 | { 356 | void *buf = NULL; 357 | if (vma->vm_flags & VM_WRITE) 358 | buf = drvdata->in_buf; 359 | else if (vma->vm_flags & VM_READ) 360 | buf = drvdata->out_buf; 361 | if (!buf) 362 | return -EINVAL; 363 | if (remap_pfn_range(vma, vma->vm_start, virt_to_phys(buf) >> PAGE_SHIFT, 364 | vma->vm_end - vma->vm_start, vma->vm_page_prot)) { 365 | pr_err("%s\n", "remap_pfn_range failed"); 366 | return -EAGAIN; 367 | } 368 | return 0; 369 | } 370 | 371 | #define trace_ioctl(nr) pr_debug("ioctl=(%c%c %c #%i %i)\n", \ 372 | (_IOC_READ & _IOC_DIR(nr)) ? 'r' : ' ', \ 373 | (_IOC_WRITE & _IOC_DIR(nr)) ? 'w' : ' ', \ 374 | _IOC_TYPE(nr), _IOC_NR(nr), _IOC_SIZE(nr)) 375 | 376 | static DEFINE_MUTEX(ioctl_lock); 377 | 378 | static long ldt_ioctl(struct file *f, unsigned int cmnd, unsigned long arg) 379 | { 380 | int ret = 0; 381 | void __user *user = (void __user *)arg; 382 | 383 | if (mutex_lock_interruptible(&ioctl_lock)) 384 | return -EINTR; 385 | pr_debug("%s:\n", __func__); 386 | pr_debug("cmnd=0x%X\n", cmnd); 387 | pr_debug("arg=0x%lX\n", arg); 388 | trace_ioctl(cmnd); 389 | switch (_IOC_TYPE(cmnd)) { 390 | case 'A': 391 | switch (_IOC_NR(cmnd)) { 392 | case 0: 393 | if (_IOC_DIR(cmnd) == _IOC_WRITE) { 394 | if (copy_from_user(drvdata->in_buf, user, 395 | _IOC_SIZE(cmnd))) { 396 | ret = -EFAULT; 397 | goto exit; 398 | } 399 | /* copy data from in_buf to out_buf to emulate loopback for testing */ 400 | memcpy(drvdata->out_buf, drvdata->in_buf, bufsize); 401 | memset(drvdata->in_buf, 0, bufsize); 402 | } 403 | if (_IOC_DIR(cmnd) == _IOC_READ) { 404 | if (copy_to_user(user, drvdata->out_buf, 405 | _IOC_SIZE(cmnd))) { 406 | ret = -EFAULT; 407 | goto exit; 408 | } 409 | memset(drvdata->out_buf, 0, bufsize); 410 | } 411 | break; 412 | } 413 | break; 414 | } 415 | exit: 416 | mutex_unlock(&ioctl_lock); 417 | return ret; 418 | } 419 | 420 | 421 | static const struct file_operations ldt_fops = { 422 | .owner = THIS_MODULE, 423 | .open = ldt_open, 424 | .release = ldt_release, 425 | .read = ldt_read, 426 | .write = ldt_write, 427 | .poll = ldt_poll, 428 | .mmap = ldt_mmap, 429 | .unlocked_ioctl = ldt_ioctl, 430 | }; 431 | 432 | static struct miscdevice ldt_miscdev = { 433 | .minor = MISC_DYNAMIC_MINOR, 434 | .name = KBUILD_MODNAME, 435 | .fops = &ldt_fops, 436 | }; 437 | 438 | /* 439 | * UART initialization section 440 | */ 441 | 442 | static struct resource *port_r; 443 | 444 | static int uart_probe(void) 445 | { 446 | int ret = 0; 447 | 448 | if (port) { 449 | /* 450 | port_r = request_region(port, port_size, KBUILD_MODNAME); 451 | if (!port_r) { 452 | pr_err("%s\n", "request_region failed"); 453 | return -EBUSY; 454 | } 455 | */ 456 | drvdata->port_ptr = ioport_map(port, port_size); 457 | pr_debug("drvdata->port_ptr=%p\n", drvdata->port_ptr); 458 | if (!drvdata->port_ptr) { 459 | pr_err("%s\n", "ioport_map failed"); 460 | return -ENODEV; 461 | } 462 | } 463 | if (!irq || !drvdata->port_ptr) 464 | goto exit; 465 | /* 466 | * Minimal configuration of UART for trivial I/O opertaions 467 | * and ISR just to porform basic tests. 468 | * Some configuration of UART is not touched and reused. 469 | * 470 | * This minimal configiration of UART is based on 471 | * full UART driver drivers/tty/serial/8250/8250.c 472 | */ 473 | ret = request_irq(irq, ldt_isr, 474 | IRQF_SHARED, KBUILD_MODNAME, THIS_MODULE); 475 | if (ret < 0) { 476 | pr_err("%s\n", "request_irq failed"); 477 | return ret; 478 | } 479 | iowrite8(UART_MCR_RTS | UART_MCR_OUT2 | UART_MCR_LOOP, 480 | drvdata->port_ptr + UART_MCR); 481 | drvdata->uart_detected = (ioread8(drvdata->port_ptr + UART_MSR) & 0xF0) 482 | == (UART_MSR_DCD | UART_MSR_CTS); 483 | 484 | if (drvdata->uart_detected) { 485 | iowrite8(UART_IER_RDI | UART_IER_RLSI | UART_IER_THRI, 486 | drvdata->port_ptr + UART_IER); 487 | iowrite8(UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2, 488 | drvdata->port_ptr + UART_MCR); 489 | iowrite8(UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT, 490 | drvdata->port_ptr + UART_FCR); 491 | pr_debug("loopback=%d\n", loopback); 492 | if (loopback) 493 | iowrite8(ioread8(drvdata->port_ptr + UART_MCR) | UART_MCR_LOOP, 494 | drvdata->port_ptr + UART_MCR); 495 | } 496 | if (!drvdata->uart_detected && loopback) 497 | pr_warn("Emulating loopback in software\n"); 498 | exit: 499 | return ret; 500 | } 501 | 502 | /* 503 | * main initialization and cleanup section 504 | */ 505 | 506 | static struct dentry *debugfs; 507 | 508 | static void ldt_cleanup(void) 509 | { 510 | debugfs_remove(debugfs); 511 | if (ldt_miscdev.this_device) 512 | misc_deregister(&ldt_miscdev); 513 | del_timer(&ldt_timer); 514 | if (irq) { 515 | if (drvdata->uart_detected) { 516 | iowrite8(0, drvdata->port_ptr + UART_IER); 517 | iowrite8(0, drvdata->port_ptr + UART_FCR); 518 | iowrite8(0, drvdata->port_ptr + UART_MCR); 519 | ioread8(drvdata->port_ptr + UART_RX); 520 | } 521 | free_irq(irq, THIS_MODULE); 522 | } 523 | tasklet_kill(&ldt_tasklet); 524 | if (drvdata->in_buf) { 525 | pages_flag(virt_to_page(drvdata->in_buf), PFN_UP(bufsize), PG_reserved, 0); 526 | free_pages_exact(drvdata->in_buf, bufsize); 527 | } 528 | if (drvdata->out_buf) { 529 | pages_flag(virt_to_page(drvdata->out_buf), PFN_UP(bufsize), PG_reserved, 0); 530 | free_pages_exact(drvdata->out_buf, bufsize); 531 | } 532 | 533 | pr_debug("isr_counter=%d\n", isr_counter); 534 | if (drvdata->port_ptr) 535 | ioport_unmap(drvdata->port_ptr); 536 | if (port_r) 537 | release_region(port, port_size); 538 | kfree(drvdata); 539 | } 540 | 541 | static struct ldt_data *ldt_data_init(void) 542 | { 543 | struct ldt_data *drvdata; 544 | 545 | drvdata = kzalloc(sizeof(*drvdata), GFP_KERNEL); 546 | if (!drvdata) 547 | return NULL; 548 | init_waitqueue_head(&drvdata->readable); 549 | init_waitqueue_head(&drvdata->writeable); 550 | INIT_KFIFO(drvdata->in_fifo); 551 | INIT_KFIFO(drvdata->out_fifo); 552 | mutex_init(&drvdata->read_lock); 553 | mutex_init(&drvdata->write_lock); 554 | return drvdata; 555 | } 556 | 557 | static int ldt_init(void) 558 | { 559 | int ret = 0; 560 | 561 | pr_debug("MODNAME=%s\n", KBUILD_MODNAME); 562 | pr_debug("port = %d irq = %d\n", port, irq); 563 | 564 | drvdata = ldt_data_init(); 565 | if (!drvdata) { 566 | pr_err("ldt_data_init failed\n"); 567 | goto exit; 568 | } 569 | 570 | /* 571 | * Allocating buffers and pinning them to RAM 572 | * to be mapped to user space in ldt_mmap 573 | */ 574 | drvdata->in_buf = alloc_pages_exact(bufsize, GFP_KERNEL | __GFP_ZERO); 575 | if (!drvdata->in_buf) { 576 | ret = -ENOMEM; 577 | goto exit; 578 | } 579 | pages_flag(virt_to_page(drvdata->in_buf), PFN_UP(bufsize), PG_reserved, 1); 580 | drvdata->out_buf = alloc_pages_exact(bufsize, GFP_KERNEL | __GFP_ZERO); 581 | if (!drvdata->out_buf) { 582 | ret = -ENOMEM; 583 | goto exit; 584 | } 585 | pages_flag(virt_to_page(drvdata->out_buf), PFN_UP(bufsize), PG_reserved, 1); 586 | isr_counter = 0; 587 | /* 588 | * This drivers without UART can be sill used 589 | * in emulation mode for testing and demonstation of work 590 | */ 591 | ret = uart_probe(); 592 | if (ret < 0) { 593 | pr_err("uart_probe failed\n"); 594 | goto exit; 595 | } 596 | mod_timer(&ldt_timer, jiffies + HZ / 10); 597 | debugfs = debugfs_create_file(KBUILD_MODNAME, S_IRUGO, NULL, NULL, &ldt_fops); 598 | if (IS_ERR(debugfs)) { 599 | ret = PTR_ERR(debugfs); 600 | pr_err("debugfs_create_file failed\n"); 601 | goto exit; 602 | } 603 | ret = misc_register(&ldt_miscdev); 604 | if (ret < 0) { 605 | pr_err("misc_register failed\n"); 606 | goto exit; 607 | } 608 | pr_debug("ldt_miscdev.minor=%d\n", ldt_miscdev.minor); 609 | 610 | exit: 611 | pr_debug("ret=%d\n", ret); 612 | if (ret < 0) 613 | ldt_cleanup(); 614 | return ret; 615 | } 616 | 617 | module_init(ldt_init); 618 | module_exit(ldt_cleanup); 619 | 620 | MODULE_DESCRIPTION("LDT - Linux Driver Template"); 621 | MODULE_AUTHOR("Constantine Shulyupin "); 622 | MODULE_LICENSE("GPL"); 623 | -------------------------------------------------------------------------------- /ldt_plat_dev.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LDT - Linux Driver Template 3 | * 4 | * Copyright (C) 2012 Constantine Shulyupin http://www.makelinux.net/ 5 | * 6 | * GPL License 7 | * 8 | * platform_device template driver 9 | * 10 | * uses 11 | * 12 | * platform_data 13 | * resources 14 | * 15 | */ 16 | 17 | #include 18 | #include 19 | 20 | static struct resource ldt_resource[] = { 21 | { 22 | .flags = IORESOURCE_IO, 23 | .start = 0x3f8, 24 | .end = 0x3ff, 25 | }, 26 | { 27 | .flags = IORESOURCE_IRQ, 28 | .start = 4, 29 | .end = 4, 30 | }, 31 | { 32 | .flags = IORESOURCE_MEM, 33 | .start = 0, 34 | .end = 0, 35 | }, 36 | }; 37 | 38 | static void ldt_dev_release(struct device *dev) 39 | { 40 | } 41 | 42 | static struct platform_device ldt_platform_device = { 43 | .name = "ldt_device_name", 44 | .resource = ldt_resource, 45 | .num_resources = ARRAY_SIZE(ldt_resource), 46 | .dev.platform_data = "test data", 47 | .dev.release = ldt_dev_release, 48 | }; 49 | 50 | static int ldt_plat_dev_init(void) 51 | { 52 | return platform_device_register(&ldt_platform_device); 53 | } 54 | 55 | static void ldt_plat_dev_exit(void) 56 | { 57 | platform_device_unregister(&ldt_platform_device); 58 | } 59 | 60 | module_init(ldt_plat_dev_init); 61 | module_exit(ldt_plat_dev_exit); 62 | 63 | MODULE_DESCRIPTION("LDT - Linux Driver Template: platform_device"); 64 | MODULE_AUTHOR("Constantine Shulyupin "); 65 | MODULE_LICENSE("GPL"); 66 | -------------------------------------------------------------------------------- /ldt_plat_drv.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LDT - Linux Driver Template 3 | * 4 | * Copyright (C) 2012 Constantine Shulyupin http://www.makelinux.net/ 5 | * 6 | * Dual BSD/GPL License 7 | * 8 | * platform_driver template driver 9 | * Power Management (dev_pm_ops) 10 | * Device Tree (of_device_id) 11 | * 12 | */ 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "tracing.h" 22 | 23 | static int irq; 24 | static int port; 25 | static int port_size; 26 | 27 | static int ldt_plat_probe(struct platform_device *pdev) 28 | { 29 | char *data = NULL; 30 | struct resource *r; 31 | struct device *dev = &pdev->dev; 32 | 33 | _entry: 34 | dev_dbg(dev, "probe\n"); 35 | data = pdev->dev.platform_data; 36 | irq = platform_get_irq(pdev, 0); 37 | r = platform_get_resource(pdev, IORESOURCE_IRQ, 0); 38 | pr_debug("pdev->dev.of_node = %p\n", pdev->dev.of_node); 39 | #ifdef CONFIG_OF_DEVICE 40 | if (pdev->dev.of_node) { 41 | const __be32 *p; 42 | int property; 43 | of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev); 44 | irq = irq_of_parse_and_map(pdev->dev.of_node, 0); 45 | p = of_get_property(pdev->dev.of_node, "property", NULL); 46 | if (p) 47 | property = be32_to_cpu(*p); 48 | (void)property; 49 | } 50 | #endif 51 | /* 52 | sample code for drvdata usage: 53 | struct ldt_data *drvdata = platform_get_drvdata(pdev); 54 | platform_set_drvdata(pdev, drvdata); 55 | */ 56 | (void)data; 57 | data = dev_get_platdata(&pdev->dev); 58 | pr_debug("%p %s\n", data, data); 59 | (void)r; 60 | r = platform_get_resource(pdev, IORESOURCE_IO, 0); 61 | port = r->start; 62 | port_size = resource_size(r); 63 | /* 64 | devm_kzalloc 65 | 66 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 67 | r = devm_request_and_ioremap(&pdev->dev, res); 68 | 69 | */ 70 | 71 | return 0; 72 | } 73 | 74 | static int ldt_plat_remove(struct platform_device *pdev) 75 | { 76 | _entry: 77 | return 0; 78 | } 79 | 80 | /* 81 | * template for OF FDT ID 82 | * (Open Firmware Flat Device Tree) 83 | */ 84 | 85 | static const struct of_device_id ldt_of_match[] = { 86 | {.compatible = "linux-driver-template"}, 87 | {}, 88 | }; 89 | 90 | MODULE_DEVICE_TABLE(of, ldt_of_match); 91 | 92 | #ifdef CONFIG_PM 93 | 94 | static int ldt_suspend(struct device *dev) 95 | { 96 | return 0; 97 | } 98 | 99 | static int ldt_resume(struct device *dev) 100 | { 101 | return 0; 102 | } 103 | 104 | static const struct dev_pm_ops ldt_pm = { 105 | .suspend = ldt_suspend, 106 | .resume = ldt_resume, 107 | }; 108 | 109 | #define ldt_pm_ops (&ldt_pm) 110 | #else 111 | #define ldt_pm_ops NULL 112 | #endif 113 | 114 | static struct platform_driver ldt_plat_driver = { 115 | .driver = { 116 | .name = "ldt_device_name", 117 | .owner = THIS_MODULE, 118 | .pm = ldt_pm_ops, 119 | .of_match_table = of_match_ptr(ldt_of_match), 120 | }, 121 | .probe = ldt_plat_probe, 122 | .remove = ldt_plat_remove, 123 | 124 | }; 125 | 126 | module_platform_driver(ldt_plat_driver); 127 | 128 | MODULE_DESCRIPTION("LDT - Linux Driver Template: platform_driver template"); 129 | MODULE_AUTHOR("Constantine Shulyupin "); 130 | MODULE_LICENSE("Dual BSD/GPL"); 131 | -------------------------------------------------------------------------------- /ldt_plat_test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sudo insmod ./ldt_plat_drv.ko 4 | sudo insmod ./ldt_plat_dev.ko 5 | dmesg 6 | sudo rmmod ldt_plat_dev ldt_plat_drv 7 | 8 | -------------------------------------------------------------------------------- /misc_loop_drv.c: -------------------------------------------------------------------------------- 1 | /* 2 | * misc_loop_drv 3 | * 4 | * Simple misc device driver sample 5 | * 6 | * Copyright (C) 2012 Constantine Shulyupin http://www.makelinux.net/ 7 | * 8 | * Licensed under the GPLv2. 9 | * 10 | * The driver demonstrates usage of following Linux facilities: 11 | * 12 | * Linux kernel module 13 | * simple single misc device file (miscdevice, misc_register) 14 | * file_operations 15 | * read and write 16 | * blocking read and write 17 | * polling 18 | * kfifo 19 | * interrupt 20 | * io 21 | * tasklet 22 | * 23 | * Run test script misc_loop_drv_test to test the driver 24 | * 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #undef pr_fmt 44 | #define pr_fmt(fmt) "%s.c:%d %s " fmt, KBUILD_MODNAME, __LINE__, __func__ 45 | 46 | /* 47 | * It's supposed you computer doesn't use floppy device. 48 | * This drivers uses IRQ and port of floppy device for demonstration. 49 | */ 50 | 51 | static int port = 0x3f4; 52 | module_param(port, int, 0); 53 | MODULE_PARM_DESC(port, "I/O port number, default 0x3f4 - floppy"); 54 | 55 | static int port_size = 2; 56 | module_param(port_size, int, 0); 57 | MODULE_PARM_DESC(port_size, "size of I/O port mapping, default 2"); 58 | 59 | static int irq = 6; 60 | module_param(irq, int, 0); 61 | MODULE_PARM_DESC(irq, "interrupt request number, default 6 - floppy"); 62 | 63 | /* 64 | * Offsets of registers in port_emulation 65 | * 66 | * Pay attention that MISC_DRV_TX == MISC_DRV_RX and MISC_DRV_TX_FULL == MISC_DRV_RX_READY. 67 | * Transmitted data becomes received and emulates port in loopback mode. 68 | */ 69 | 70 | #define MISC_DRV_TX 0 71 | #define MISC_DRV_RX 0 72 | #define MISC_DRV_TX_FULL 1 73 | #define MISC_DRV_RX_READY 1 74 | 75 | static char port_emulation[2]; /* array to emulate I/0 port */ 76 | 77 | #define FIFO_SIZE 128 /* must be power of two */ 78 | 79 | /** 80 | * struct misc_loop_drv_data - the driver data 81 | * @in_fifo: input queue for write 82 | * @out_fifo: output queue for read 83 | * @fifo_lock: lock for queues 84 | * @readable: waitqueue for blocking read 85 | * @writeable: waitqueue for blocking write 86 | * @port_ptr: mapped io port 87 | * 88 | * Can be retrieved from platform_device with 89 | * struct misc_loop_drv_data *drvdata = platform_get_drvdata(pdev); 90 | */ 91 | 92 | struct misc_loop_drv_data { 93 | struct mutex read_lock; 94 | struct mutex write_lock; 95 | DECLARE_KFIFO(in_fifo, char, FIFO_SIZE); 96 | DECLARE_KFIFO(out_fifo, char, FIFO_SIZE); 97 | spinlock_t fifo_lock; 98 | wait_queue_head_t readable, writeable; 99 | struct tasklet_struct misc_loop_drv_tasklet; 100 | void __iomem *port_ptr; 101 | struct resource *port_res; 102 | }; 103 | 104 | static struct misc_loop_drv_data *drvdata; 105 | 106 | static void misc_loop_drv_tasklet_func(unsigned long d) 107 | { 108 | char data_out, data_in; 109 | struct misc_loop_drv_data *drvdata = (void *)d; 110 | 111 | while (!ioread8(drvdata->port_ptr + MISC_DRV_TX_FULL) 112 | && kfifo_out_spinlocked(&drvdata->out_fifo, 113 | &data_out, sizeof(data_out), &drvdata->fifo_lock)) { 114 | wake_up_interruptible(&drvdata->writeable); 115 | pr_debug("data_out=%d %c\n", data_out, data_out >= 32 ? data_out : ' '); 116 | iowrite8(data_out, drvdata->port_ptr + MISC_DRV_TX); 117 | /* set full tx flag and implicitly rx ready flag */ 118 | iowrite8(1, drvdata->port_ptr + MISC_DRV_TX_FULL); 119 | /* 120 | In regular drivers hardware invokes interrupts. 121 | Because this drivers works without real hardware 122 | we simulate interrupt invocation with function send_IPI_all. 123 | In driver, which works with real hardware this is not required. 124 | */ 125 | apic->send_IPI_all(ISA_IRQ_VECTOR(irq)); 126 | } 127 | while (ioread8(drvdata->port_ptr + MISC_DRV_RX_READY)) { 128 | data_in = ioread8(drvdata->port_ptr + MISC_DRV_RX); 129 | pr_debug("data_in=%d %c\n", data_in, data_in >= 32 ? data_in : ' '); 130 | kfifo_in_spinlocked(&drvdata->in_fifo, &data_in, 131 | sizeof(data_in), &drvdata->fifo_lock); 132 | wake_up_interruptible(&drvdata->readable); 133 | /* clear rx ready flag and implicitly tx full flag */ 134 | iowrite8(0, drvdata->port_ptr + MISC_DRV_RX_READY); 135 | } 136 | } 137 | 138 | static irqreturn_t misc_loop_drv_isr(int irq, void *d) 139 | { 140 | struct misc_loop_drv_data *drvdata = (void *)d; 141 | 142 | tasklet_schedule(&drvdata->misc_loop_drv_tasklet); 143 | return IRQ_HANDLED; 144 | } 145 | 146 | static int misc_loop_drv_open(struct inode *inode, struct file *file) 147 | { 148 | pr_debug("from %s\n", current->comm); 149 | /* client related data can be allocated here and 150 | stored in file->private_data */ 151 | return 0; 152 | } 153 | 154 | static int misc_loop_drv_release(struct inode *inode, struct file *file) 155 | { 156 | pr_debug("from %s\n", current->comm); 157 | /* client related data can be retrieved from file->private_data 158 | and released here */ 159 | return 0; 160 | } 161 | 162 | static ssize_t misc_loop_drv_read(struct file *file, char __user *buf, 163 | size_t count, loff_t *ppos) 164 | { 165 | int ret = 0; 166 | unsigned int copied; 167 | 168 | pr_debug("from %s\n", current->comm); 169 | if (kfifo_is_empty(&drvdata->in_fifo)) { 170 | if (file->f_flags & O_NONBLOCK) { 171 | return -EAGAIN; 172 | } else { 173 | pr_debug("%s\n", "waiting"); 174 | ret = wait_event_interruptible(drvdata->readable, 175 | !kfifo_is_empty(&drvdata->in_fifo)); 176 | if (ret == -ERESTARTSYS) { 177 | pr_err("interrupted\n"); 178 | return -EINTR; 179 | } 180 | } 181 | } 182 | if (mutex_lock_interruptible(&drvdata->read_lock)) 183 | return -EINTR; 184 | ret = kfifo_to_user(&drvdata->in_fifo, buf, count, &copied); 185 | mutex_unlock(&drvdata->read_lock); 186 | 187 | return ret ? ret : copied; 188 | } 189 | 190 | static ssize_t misc_loop_drv_write(struct file *file, const char __user *buf, 191 | size_t count, loff_t *ppos) 192 | { 193 | int ret; 194 | unsigned int copied; 195 | 196 | pr_debug("from %s\n", current->comm); 197 | if (kfifo_is_full(&drvdata->out_fifo)) { 198 | if (file->f_flags & O_NONBLOCK) { 199 | return -EAGAIN; 200 | } else { 201 | ret = wait_event_interruptible(drvdata->writeable, 202 | !kfifo_is_full(&drvdata->out_fifo)); 203 | if (ret == -ERESTARTSYS) { 204 | pr_err("interrupted\n"); 205 | return -EINTR; 206 | } 207 | } 208 | } 209 | if (mutex_lock_interruptible(&drvdata->write_lock)) 210 | return -EINTR; 211 | ret = kfifo_from_user(&drvdata->out_fifo, buf, count, &copied); 212 | mutex_unlock(&drvdata->write_lock); 213 | tasklet_schedule(&drvdata->misc_loop_drv_tasklet); 214 | 215 | return ret ? ret : copied; 216 | } 217 | 218 | static unsigned int misc_loop_drv_poll(struct file *file, poll_table *pt) 219 | { 220 | unsigned int mask = 0; 221 | poll_wait(file, &drvdata->readable, pt); 222 | poll_wait(file, &drvdata->writeable, pt); 223 | 224 | if (!kfifo_is_empty(&drvdata->in_fifo)) 225 | mask |= POLLIN | POLLRDNORM; 226 | mask |= POLLOUT | POLLWRNORM; 227 | /* 228 | if case of output end of file set 229 | mask |= POLLHUP; 230 | in case of output error set 231 | mask |= POLLERR; 232 | */ 233 | return mask; 234 | } 235 | 236 | static const struct file_operations misc_loop_drv_fops = { 237 | .owner = THIS_MODULE, 238 | .open = misc_loop_drv_open, 239 | .release = misc_loop_drv_release, 240 | .read = misc_loop_drv_read, 241 | .write = misc_loop_drv_write, 242 | .poll = misc_loop_drv_poll, 243 | }; 244 | 245 | static struct miscdevice misc_loop_drv_dev = { 246 | .minor = MISC_DYNAMIC_MINOR, 247 | .name = KBUILD_MODNAME, 248 | .fops = &misc_loop_drv_fops, 249 | }; 250 | 251 | /* 252 | * Initialization and cleanup section 253 | */ 254 | 255 | static void misc_loop_drv_cleanup(void) 256 | { 257 | if (misc_loop_drv_dev.this_device) 258 | misc_deregister(&misc_loop_drv_dev); 259 | if (irq) 260 | free_irq(irq, drvdata); 261 | tasklet_kill(&drvdata->misc_loop_drv_tasklet); 262 | 263 | if (drvdata->port_ptr) 264 | ioport_unmap(drvdata->port_ptr); 265 | if (drvdata->port_res) 266 | release_region(port, port_size); 267 | kfree(drvdata); 268 | } 269 | 270 | static struct misc_loop_drv_data *misc_loop_drv_data_init(void) 271 | { 272 | struct misc_loop_drv_data *drvdata; 273 | 274 | drvdata = kzalloc(sizeof(*drvdata), GFP_KERNEL); 275 | if (!drvdata) 276 | return NULL; 277 | init_waitqueue_head(&drvdata->readable); 278 | init_waitqueue_head(&drvdata->writeable); 279 | INIT_KFIFO(drvdata->in_fifo); 280 | INIT_KFIFO(drvdata->out_fifo); 281 | mutex_init(&drvdata->read_lock); 282 | mutex_init(&drvdata->write_lock); 283 | tasklet_init(&drvdata->misc_loop_drv_tasklet, 284 | misc_loop_drv_tasklet_func, (unsigned long)drvdata); 285 | return drvdata; 286 | } 287 | 288 | static int misc_loop_drv_init(void) 289 | { 290 | int ret = 0; 291 | 292 | pr_debug("MODNAME=%s\n", KBUILD_MODNAME); 293 | pr_debug("port = %X irq = %d\n", port, irq); 294 | 295 | drvdata = misc_loop_drv_data_init(); 296 | if (!drvdata) { 297 | pr_err("misc_loop_drv_data_init failed\n"); 298 | goto exit; 299 | } 300 | 301 | drvdata->port_res = request_region(port, port_size, KBUILD_MODNAME); 302 | if (!drvdata->port_res) { 303 | pr_err("request_region failed\n"); 304 | return -EBUSY; 305 | } 306 | /* 307 | Real I/O port should be mapped with function with ioport_map: 308 | drvdata->port_ptr = ioport_map(port, port_size); 309 | But, because we work in emulation mode, we use array instead mapped ports 310 | */ 311 | drvdata->port_ptr = (void __iomem *) port_emulation; 312 | if (!drvdata->port_ptr) { 313 | pr_err("ioport_map failed\n"); 314 | return -ENODEV; 315 | } 316 | /* clear ports */ 317 | iowrite8(0, drvdata->port_ptr + MISC_DRV_TX); 318 | iowrite8(0, drvdata->port_ptr + MISC_DRV_TX_FULL); 319 | 320 | ret = misc_register(&misc_loop_drv_dev); 321 | if (ret < 0) { 322 | pr_err("misc_register failed\n"); 323 | goto exit; 324 | } 325 | pr_debug("misc_loop_drv_dev.minor=%d\n", misc_loop_drv_dev.minor); 326 | ret = request_irq(irq, misc_loop_drv_isr, 0, KBUILD_MODNAME, drvdata); 327 | if (ret < 0) { 328 | pr_err("request_irq failed\n"); 329 | return ret; 330 | } 331 | 332 | exit: 333 | pr_debug("ret=%d\n", ret); 334 | if (ret < 0) 335 | misc_loop_drv_cleanup(); 336 | return ret; 337 | } 338 | 339 | module_init(misc_loop_drv_init); 340 | module_exit(misc_loop_drv_cleanup); 341 | 342 | MODULE_DESCRIPTION("misc_loop_drv Simple misc device driver sample"); 343 | MODULE_AUTHOR("Constantine Shulyupin "); 344 | MODULE_LICENSE("GPL"); 345 | -------------------------------------------------------------------------------- /misc_loop_drv_test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # The script is intended to be run from user root 4 | 5 | RED="\\033[0;31m" 6 | NOCOLOR="\\033[0;39m" 7 | GREEN="\\033[0;32m" 8 | 9 | dmesg -c > /dev/null # clear log buffer 10 | # It's supposed you computer doesn't use floppy device. 11 | # misc_loop_drv uses IRQ 6 of floppy device demonstrate IRQ usage. 12 | rmmod floppy 2> /dev/null 13 | rmmod misc_loop_drv 2> /dev/null 14 | insmod ./misc_loop_drv.ko 15 | 16 | data=123rw 17 | cat /dev/misc_loop_drv > R.tmp & 18 | sleep 0.1; echo $data > /dev/misc_loop_drv; 19 | sleep 0.5 20 | kill %1; wait %1 2> /dev/null || true 21 | received=`cat R.tmp` 22 | rm -f R.tmp 23 | 24 | if [ "$data" == "$received" ]; then 25 | echo -e "${GREEN}misc_loop_drv blocking read/write test passed$NOCOLOR" 26 | else 27 | echo -e "${RED}misc_loop_drv blocking read/write test failed$NOCOLOR" 28 | echo expected $data 29 | echo received $received 30 | fi 31 | 32 | data=123nb 33 | echo -n $data > /dev/misc_loop_drv 34 | sleep 0.5 35 | received=`dd iflag=nonblock if=/dev/misc_loop_drv 2> /dev/null ` 36 | if [ "$data" == "$received" ]; then 37 | echo -e "${GREEN}misc_loop_drv nonblocking read/write test passed$NOCOLOR" 38 | else 39 | echo -e "${RED}misc_loop_drv nonblock read/write test failed$NOCOLOR" 40 | echo expected $data 41 | echo received $received 42 | fi 43 | 44 | rmmod misc_loop_drv.ko 45 | dmesg --notime --show-delta --read-clear 2>/dev/null > kernel.log || \ 46 | dmesg -c > kernel.log && echo kernel.log saved 47 | -------------------------------------------------------------------------------- /pci-ldt.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include 4 | 5 | MODULE_DESCRIPTION("PCI Linux Driver Template"); 6 | 7 | /* 8 | * Usage: 9 | * 10 | * 1. Prefer to use a hypervisor, for example VirtualBox. 11 | * 2. Define or select a non-critical PCI device for use. 12 | * For example, you can add Audio controller ICH AC97 in VirtualBox VM 13 | * 3. Using the command lspci, find a corresponding device driver. 14 | * For example, ICH AC97 is used by driver snd_intel8x0. 15 | * 4. Remove the default driver of the selected device: sudo rmmod snd_intel8x0 16 | * 5. Compile and insert this driver: sudo insmod pci-ldt.ko 17 | * The driver tries to bind to every PCI device and succeeds to bind 18 | * only to unused devices, including the device selected above. 19 | * 6. Then remove this driver: sudo rmmod pci-ldt 20 | * 7. Analyze the kernel log: dmesg 21 | * Successful driver usage acquires a base address and calls dummy interrupts. 22 | * 8. Explore the source. 23 | * 9. Read https://en.wikibooks.org/wiki/The_Linux_Kernel/PCI 24 | * 10. Improve the driver and send a pull request. 25 | */ 26 | 27 | MODULE_AUTHOR("Costa Shulyupin , "); 28 | MODULE_LICENSE("GPL v2"); 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #define CTRACER_ON 43 | #include "ctracer.h" 44 | 45 | #undef pr_fmt 46 | #define pr_fmt(fmt) "%s.c:%d %s " fmt, KBUILD_MODNAME, __LINE__, __func__ 47 | 48 | /** 49 | * struct instance_data - Internal data for one instance of device 50 | * @pcid: PCI device 51 | */ 52 | 53 | struct instance_data { 54 | struct pci_dev *pcid; 55 | /* Add here custom data of the driver */ 56 | void __iomem *base; 57 | }; 58 | 59 | static int irqs; 60 | 61 | static irqreturn_t pci_ldt_irq(int irq, struct instance_data *d) 62 | { 63 | if (!irqs) { 64 | dev_info(&d->pcid->dev, "First IRQ"); 65 | #if KERNEL_VERSION(5, 6, 0) <= LINUX_VERSION_CODE 66 | dev_info(&d->pcid->dev, "%x\n", pci_status_get_and_clear_errors(d->pcid)); 67 | #endif 68 | } 69 | /* TODO: check interrupt */ 70 | return IRQ_WAKE_THREAD; 71 | } 72 | 73 | static irqreturn_t pci_ldt_th(int irq, struct instance_data *d) 74 | { 75 | /* TODO: process interrupt 76 | return IRQ_HANDLED; 77 | */ 78 | ++irqs; 79 | return IRQ_NONE; 80 | } 81 | 82 | static void pci_ldt_free(struct pci_dev *pcid) 83 | { 84 | pci_free_irq_vectors(pcid); 85 | pcim_iounmap_regions(pcid, pci_select_bars(pcid, IORESOURCE_MEM)); 86 | pci_disable_device(pcid); 87 | } 88 | 89 | static int pci_ldt_probe(struct pci_dev *pcid, const struct pci_device_id *ent) 90 | { 91 | struct instance_data *data; 92 | u16 status = ~0; 93 | int ret; 94 | int bar; 95 | 96 | dev_dbg(&pcid->dev, "Probing %04X:%04X\n", pcid->vendor, pcid->device); 97 | 98 | ret = pci_enable_device_mem(pcid); 99 | if (ret) { 100 | dev_err(&pcid->dev, "pci_enable_device_mem %d\n", ret); 101 | goto error; 102 | } 103 | ret = pcim_iomap_regions(pcid, pci_select_bars(pcid, IORESOURCE_MEM), pci_name(pcid)); 104 | if (ret) { 105 | dev_err(&pcid->dev, "pcim_iomap_regions %d\n", ret); 106 | goto error; 107 | } 108 | 109 | bar = ffs(pci_select_bars(pcid, IORESOURCE_MEM)) - 1; 110 | data = devm_kzalloc(&pcid->dev, sizeof(*data), GFP_KERNEL); 111 | if (!data) { 112 | ret = -ENOMEM; 113 | goto error; 114 | } 115 | data->pcid = pcid; 116 | pci_set_drvdata(pcid, data); 117 | ret = pci_alloc_irq_vectors(pcid, 1, 1, PCI_IRQ_ALL_TYPES); 118 | if (ret < 1) { 119 | pci_ldt_free(pcid); 120 | return -ENODEV; 121 | } 122 | ret = pci_request_irq(pcid, 0, (void *)pci_ldt_irq, (void *)pci_ldt_th, data, "%s", pci_name(pcid)); 123 | if (ret) { 124 | dev_err(&pcid->dev, "request_irq failed\n"); 125 | goto error; 126 | } 127 | 128 | 129 | dev_notice(&pcid->dev, "Added %04X:%04X\n", pcid->vendor, pcid->device); 130 | pci_read_config_word(pcid, PCI_STATUS, &status); 131 | trlvx(status); 132 | 133 | if (bar >= 0) { 134 | data->base = pcim_iomap_table(pcid)[bar]; 135 | trvp(data->base); 136 | print_hex_dump(KERN_DEBUG, "regs:", DUMP_PREFIX_OFFSET, 137 | 16, 2, data->base, 64, 0); 138 | } 139 | 140 | return 0; 141 | error: 142 | dev_dbg(&pcid->dev, "Error %d adding %04X:%04X\n", ret, pcid->vendor, pcid->device); 143 | pci_ldt_free(pcid); 144 | return ret; 145 | } 146 | 147 | static void pci_ldt_remove(struct pci_dev *pcid) 148 | { 149 | trl(); 150 | 151 | trlvd(irqs); 152 | pci_free_irq(pcid, 0, pci_get_drvdata(pcid)); 153 | pci_ldt_free(pcid); 154 | dev_notice(&pcid->dev, "Removed %04X:%04X\n", pcid->vendor, pcid->device); 155 | } 156 | 157 | static const struct pci_device_id pci_ldt_ids[] = { 158 | /* For demonstration the driver will be probed with all devices, 159 | * and loaded only for unused by another driver. 160 | * TODO: Limit this IDs for your device. 161 | */ 162 | { PCI_DEVICE(PCI_ANY_ID, PCI_ANY_ID) }, 163 | { } 164 | }; 165 | MODULE_DEVICE_TABLE(pci, pci_ldt_ids); 166 | 167 | static struct pci_driver pci_ldt_driver = { 168 | .name = KBUILD_MODNAME, 169 | .probe = pci_ldt_probe, 170 | .remove = pci_ldt_remove, 171 | .id_table = pci_ldt_ids, 172 | }; 173 | 174 | module_pci_driver(pci_ldt_driver); 175 | -------------------------------------------------------------------------------- /tracing.h: -------------------------------------------------------------------------------- 1 | 2 | #define multistatement(ms) ms /* trick to bypass checkpatch.pl error */ 3 | #define _entry multistatement(goto _entry; _entry) 4 | 5 | /* 6 | * ctracer_cut_paths - return filename without path 7 | */ 8 | 9 | #define trace_loc() printk(KERN_DEBUG"%s:%d %s ", __file__, __LINE__, __func__) 10 | #define trace_hex(h) printk("%s = 0x%lX ", #h, (long int)h) 11 | #define trace_dec(d) printk("%s = %ld ", #d, (long int)d) 12 | #define trace_dec_ln(d) printk("%s = %ld\n", #d, (long int)d) 13 | #define trace_ln(m) printk(KERN_CONT"\n") 14 | 15 | #define ctracer_cut_path(fn) (fn[0] != '/' ? fn : (strrchr(fn, '/') + 1)) 16 | #define __file__ ctracer_cut_path(__FILE__) 17 | 18 | /* 19 | * print_context prints execution context: 20 | * hard interrupt, soft interrupt or scheduled task 21 | */ 22 | 23 | #define print_context() \ 24 | pr_debug("%s:%d %s %s 0x%x\n", __file__, __LINE__, __func__, \ 25 | (in_irq() ? "harirq" : current->comm), preempt_count()); 26 | 27 | #define once(exp) do { \ 28 | static int _passed; if (!_passed) { exp; }; _passed = 1; } while (0) 29 | 30 | #define check(a) \ 31 | (ret = a, ((ret < 0) ? pr_warn("%s:%i %s FAIL\n\t%i=%s\n", \ 32 | __file__, __LINE__, __func__, ret, #a) : 0), ret) 33 | 34 | #define pr_debug_hex(h) pr_debug("%s:%d %s %s = 0x%lX\n", \ 35 | __file__, __LINE__, __func__, #h, (long int)h) 36 | #define pr_debug_dec(d) pr_debug("%s:%d %s %s = %ld\n", \ 37 | __file__, __LINE__, __func__, #d, (long int)d) 38 | 39 | #define pr_err_msg(m) pr_err("%s:%d %s %s\n", __file__, __LINE__, __func__, m) 40 | --------------------------------------------------------------------------------