This article introduces the basics of Linux character devices, discussing in detail the concept of device numbers (major and minor numbers), the dev_t type and its related operation macros, and summarizes the usage of static and dynamic allocation and release functions for device numbers, while also mentioning the cdev structure related to character device registration.
Function purpose: Statically allocate a device number, applying for a specified device number.
Parameter meaning:
from: Custom dev_t type device number, e.g., MKDEV(100,0) indicates starting major number 100 and starting minor number 0.
count: The number of minor devices, indicating how many minor device numbers exist under the same major device number.
name: The name of the device being allocated.
Function return value: Returns 0 on success, negative on failure.
Note that statically allocated device numbers are limited:include/linux/fs.hDefined in:
1 2 3 4 5 6 7
/* fs/char_dev.c */ #define CHRDEV_MAJOR_MAX 512 /* Marks the bottom of the first segment of free char majors */ #define CHRDEV_MAJOR_DYN_END 234 /* Marks the top and bottom of the second segment of free char majors */ #define CHRDEV_MAJOR_DYN_EXT_START 511 #define CHRDEV_MAJOR_DYN_EXT_END 384
alloc_chrdev_region()
Throughalloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char* name)the function to dynamically allocate device numbers.
Function purpose: Dynamically allocate a device number; the kernel automatically assigns an unused device number. Compared to static allocation, dynamic allocation avoids conflicts caused by registering the same device number.
Parameter meaning:
dev *: Saves the allocated device number in the dev variable
baseminor: Starting address of the minor device number; minor device numbers usually start from 0, so this parameter is generally set to 0
count: Number of devices to allocate
name: Name of the device to allocate
Function return value: Returns 0 on success, negative on failure
In C language, if you only need to use a pointer to a certain structpointerwithout needing to know its internal members (i.e., without dereferencing or using sizeof), you can simply do aforward declaration:
1
structfile_operations;// Tell the compiler: there exists a struct type called file_operations.
This way you can define a pointer:
1
structfile_operations *fops;
But you cannot access members (because the compiler does not yet know the struct’s contents):
1
fops->read(...); // ❌ Compilation error: incomplete type
This is very common in header files, used todecouple dependenciesandspeed up compilation。
whileexternisa storage class specifier, used to declarevariablesorfunctionswith external linkage. Its purpose is to tell the compiler: ‘This variable/function is defined elsewhere; here it is only a declaration.’
GCC/Clang attribute(__randomize_layout), Used for the kernel’sstruct layout randomization, it increases the difficulty for attackers to predict struct offsets, thereby improving kernel security.
cdev structure
Field
Meaning
kobj
Inherits fromstruct kobject, enabling character devices to be mounted under sysfs (i.e.,/sys/class/...paths such as).
owner
Points to the module that owns the device (THIS_MODULE), preventing the device from being in use when the module is unloaded.
ops
Points to the device operation function table (struct file_operations), defining behaviors such as read/write/ioctl.
list
Internal linked list, linking all inodes that share thiscdev(i.e.,/dev/xxxfiles) throughinode->i_devicesto form a “reverse reference linked list”.
dev
Device number, type isdev_t(including major device number and minor device number).
count
indicates thiscdevthe number of consecutive device numbers controlled (usually 1).
cdev_init
fs/char_dev.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/** * cdev_init() - initialize a cdev structure * @cdev: the structure to initialize * @fops: the file_operations for this device * * Initializes @cdev, remembering @fops, making it ready to add to the * system with cdev_add(). */ voidcdev_init(struct cdev *cdev, conststruct file_operations *fops) { memset(cdev, 0, sizeof *cdev); INIT_LIST_HEAD(&cdev->list); kobject_init(&cdev->kobj, &ktype_cdev_default); cdev->ops = fops; }
Function: initialize a statically allocatedstruct cdev. Establish the connection between cdev and file_operations.
Note: after callingcdev_initit is best to callcdev_test.owner = THIS_MODULE;to point the owner field to this module, which prevents unloading the module while its operations are in use.
/** * cdev_add() - add a char device to the system * @p: the cdev structure for the device * @dev: the first device number for which this device is responsible * @count: the number of consecutive minor numbers corresponding to this * device * * cdev_add() adds the device represented by @p to the system, making it * live immediately. A negative error code is returned on failure. */ intcdev_add(struct cdev *p, dev_t dev, unsigned count) { int error;
Function: add a cdev structure to the system, i.e., add a character device.
cdev_map: globalkobj_mapstructure (hash table)
p: points tostruct cdev
Note:cdevis embedded instruct kobject kobj, socdevcan be regarded as akobject
cdev_del
fs/char_dev.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/** * cdev_del() - remove a cdev from the system * @p: the cdev structure to be removed * * cdev_del() removes @p from the system, possibly freeing the structure * itself. * * NOTE: This guarantees that cdev device will no longer be able to be * opened, however any cdevs already open will remain and their fops will * still be callable even after cdev_del returns. */ voidcdev_del(struct cdev *p) { cdev_unmap(p->dev, p->count); kobject_put(&p->kobj); }
Function: delete a character device from the system
Note: delete the device first, then release the device number.
file_operations structure
Linux has a very important concept called ‘everything is a file’, meaning that devices in Linux are like ordinary files. Accessing a device is like accessing a file. In applications, we can use system calls such as open, read, write, close, and ioctl to operate the driver. When we call the open function in the application, it ultimately executes the open function in the driver. Thus, file_operations connects system calls with the driver.
pos: indicates the starting position in the file where data should be written
Return value:
Success: number of bytes actually written
Error: negative errno
write steps
Check for errors or invalid requests from user space. This step is only meaningful if the device provides memory (EEPROM, I/O memory, etc.), which may have memory size limits:
1 2 3 4 5
/* If attempting to write beyond the end of the file, return an error * Here,filesizecorresponds to the size of the device memory(if any) */
if ( *pos >= filesize ) return -EINVAL;
Adjust count for the remaining bytes so as not to exceed the file size. This step is also not necessary, related to the same condition as in step (1):
1 2 3
/* The file size is forced to match the device memory size. */ if (*pos + count > filesize) count = filesize - *pos;
Find the starting position for writing. This step is relevant only when the device has memory and the write() method is to write the specified data into it. Like step (2), this step is not mandatory:
1 2
/* Convert pos to a valid address.*/ void *from = pos_to_address( *pos );
Copy data from user space and write it to the corresponding kernel space:
staticint __init cdev_test_init(void) { int err; if (major) { dev_num = MKDEV(major, minor); err = register_chrdev_region(dev_num, 1, "cdev test dev num"); if (err < 0) { pr_err("register_chrdev_region error\n"); goto chrdev_region_err; } pr_info("register_chrdev_region success\n"); } else { err = alloc_chrdev_region(&dev_num, 0, 1, "cdev test dev num"); if (err < 0) { pr_err("alloc_chrdev_region error\n"); goto chrdev_region_err; } pr_info("alloc_chrdev_region success\n"); } pr_info("dev_num: major[%d] minor[%d]", MAJOR(dev_num), MINOR(dev_num));
cdev_init(&cdev, &fops); pr_info("cdev_init success\n"); cdev.owner = THIS_MODULE; // Point owner to this module to prevent unloading the module during cdev operations
MODULE_LICENSE("GPL"); MODULE_AUTHOR("even629<asqwgo@163.com>"); MODULE_DESCRIPTION("This is just a cdev test sample");
Device node
In the Linux operating system, everything is a file. Forthe file used for device access is called a device node. By operating this “device file”, the program can control the corresponding hardware.
1
fd = open("/dev/hello", O_RDWR);
This “device file” is the device node. Therefore, the Linux device node serves as a bridge between applications and drivers. Device nodes are created under the /dev directory.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
[zhaohang@cyberboy linux_driver_learning]$ ll /dev crw------- 4,15 root 11 Nov 10:05 tty15 crw------- 4,16 root 11 Nov 10:05 tty16 crw------- 4,17 root 11 Nov 10:05 tty17 crw------- 4,18 root 11 Nov 10:05 tty18 crw------- 4,19 root 11 Nov 10:05 tty19 crw------- 4,20 root 11 Nov 10:05 tty20 crw------- 4,21 root 11 Nov 10:05 tty21 crw------- 4,22 root 11 Nov 10:05 tty22 crw------- 4,23 root 11 Nov 10:05 tty23 crw------- 4,24 root 11 Nov 10:05 tty24 crw------- 4,25 root 11 Nov 10:05 tty25 crw------- 4,26 root 11 Nov 10:05 tty26 crw------- 4,27 root 11 Nov 10:05 tty27 crw------- 4,28 root 11 Nov 10:05 tty28 crw------- 4,29 root 11 Nov 10:05 tty29
4,15Indicates the major and minor device numbers. In crw, ‘c’ stands for character device.
Depending on the method of creating device nodes, they are divided intoManual creation of device nodesandAutomatic creation of device nodes。
Methods of creating nodes under Linux
Manual creation of device nodes
Use the mknod command to manually create a device node. The format of the mknod command is:
1
$ mknod NAME TYPE MAJOR MINOR
Parameter meanings:
NAME: Name of the node to be created
TYPE: b indicates a block device, c indicates a character device, p indicates a pipe
MAJOR: Major device number of the device to link
MINOR: Minor device number of the device to link
For example: Use the following command to create a character device node named device_test, with major device number 236 and minor device number 0.
Automatically creating device nodes is achieved using theudevmechanism.
udev is a user-space program, which can detect the status of hardware devices in the system andcreate or delete device files based on the status of hardware devices in the system。
Automatically creating device nodesIn the driver, you first need to use the class_create() function to create a class, which will be located in the /sys/class/ directory.,**Then use the device_create() function to create the corresponding device under this class.**When loading the driver module, udev in user space will automatically respond and create device nodes based on the information under /sys/class/.
In embedded Linux, we use mdev,**mdev is a simplified version of udev.**When using a root filesystem built with busybox, busybox automatically creates mdev.
class_create function
linux/device/class.h
Include the header file using linux/device/device.h because device.h references class.h.
1 2 3 4 5 6 7 8
/* This is a #define to keep the compiler from merging different * instances of the __key variable */ #define class_create(owner, name) \ ({ \ static struct lock_class_key __key; \ __class_create(owner, name, &__key); \ })
Function purpose: Used to dynamically create a device class.
Parameter meaning:
owner: A pointer to a struct module type. Generally assigned as THIS_MODULE.
name: A pointer of char type, representing the name of the struct class variable to be created.
Return value:struct class *: A structure of type struct class
class_destroy function
linux/device/class.h
1
externvoidclass_destroy(struct class *cls);
Function purpose: used to delete a device class.
Parameter meaning:
cls: callclass_create()returned by the functionstruct classpointer to the structure.
device_create function
linux/device/device.h
After creating the class using class_create, you also need to use device_create function to create a device under the class.
MODULE_LICENSE("GPL"); MODULE_AUTHOR("even629<asqwgo@163.com>"); MODULE_DESCRIPTION("This is just mknod test sample");
Test:
1 2 3 4 5 6 7 8 9 10 11 12 13
$ insmod mknod_test.ko [ 11.793662] mknod_test: loading out-of-tree module taints kernel. [ 11.803110] mknod_test module init [ 11.803371] alloc_chrdev_region success [ 11.804103] dev_t dev_num: major[241], minor[0] $ ls /dev/mknod_test_device /dev/mknod_test_device $ cat /dev/mknod_test_device [ 22.546620] This is mknod_test open [ 22.547827] This is mknodtestread [ 22.548435] This is mknodtest release $ rmmod mknod_test.ko [ 28.103036] mknod_test module exit
Data exchange between user space and kernel space
Memory in kernel space and user space cannot access each other. However, many applications need to exchange data with the kernel, for example, an application usesread()a function to read data from the driver, useswrite()To write data to the driver, the above functionality requires usingcopy_from_user()andcopy_to_user()two functions to complete.
copy_from_user()The function copies data from user space to kernel space.
copy_to_user()The function copies data from kernel space to user space.
/* * Architectures should provide two primitives (raw_copy_{to,from}_user()) * and get rid of their private instances of copy_{to,from}_user() and * __copy_{to,from}_user{,_inatomic}(). * * raw_copy_{to,from}_user(to, from, size) should copy up to size bytes and * return the amount left to copy. They should assume that access_ok() has * already been checked (and succeeded); they should *not* zero-pad anything. * No KASAN or object size checks either - those belong here. * * Both of these functions should attempt to copy size bytes starting at from * into the area starting at to. They must not fetch or store anything * outside of those areas. Return value must be between 0 (everything * copied successfully) and size (nothing copied). * * If raw_copy_{to,from}_user(to, from, size) returns N, size - N bytes starting * at to must become equal to the bytes fetched from the corresponding area * starting at from. All data past to + size - N must be left unmodified. * * If copying succeeds, the return value must be 0. If some data cannot be * fetched, it is permitted to copy less than had been fetched; the only * hard requirement is that not storing anything at all (i.e. returning size) * should happen only when nothing could be copied. In other words, you don't * have to squeeze as much as possible - it is allowed, but not necessary. * * For raw_copy_from_user() to always points to kernel memory and no faults * on store should happen. Interpretation of from is affected by set_fs(). * For raw_copy_to_user() it's the other way round. * * Both can be inlined - it's up to architectures whether it wants to bother * with that. They should not be used directly; they are used to implement * the 6 functions (copy_{to,from}_user(), __copy_{to,from}_user_inatomic()) * that are used instead. Out of those, __... ones are inlined. Plain * copy_{to,from}_user() might or might not be inlined. If you want them * inlined, have asm/uaccess.h define INLINE_COPY_{TO,FROM}_USER. * * NOTE: only copy_from_user() zero-pads the destination in case of short copy. * Neither __copy_from_user() nor __copy_from_user_inatomic() zero anything * at all; their callers absolutely must check the return value. * * Biarch ones should also provide raw_copy_in_user() - similar to the above, * but both source and destination are __user pointers (affected by set_fs() * as usual) and both source and destination can trigger faults. */
/** * __copy_to_user_inatomic: - Copy a block of data into user space, with less checking. * @to: Destination address, in user space. * @from: Source address, in kernel space. * @n: Number of bytes to copy. * * Context: User context only. * * Copy data from kernel space to user space. Caller must check * the specified block with access_ok() before calling this function. * The caller should also make sure he pins the user space address * so that we don't result in page fault and sleep. */ static __always_inline __must_check unsignedlong __copy_to_user_inatomic(void __user *to, constvoid *from, unsignedlong n) { if (should_fail_usercopy()) return n; instrument_copy_to_user(to, from, n); check_object_size(from, n, true); return raw_copy_to_user(to, from, n); }
#ifndef copy_mc_to_kernel /* * Without arch opt-in this generic copy_mc_to_kernel() will not handle * #MC (or arch equivalent) during source read. */ staticinlineunsignedlong __must_check copy_mc_to_kernel(void *dst, constvoid *src, size_t cnt) { memcpy(dst, src, cnt); return0; } #endif
The overall call chain is as follows
1 2 3 4 5 6 7 8 9
copy_from_user(to, from, n) └─ check_copy_size(to, n, false) // Compile-time object size check (FORTIFY_SOURCE) └─ _copy_from_user(to, from, n) ├─ might_fault() // Mark potential page faults (scheduling points) ├─ should_fail_usercopy() // Fault injection (for testing) ├─ access_ok(from, n) // Check if the user pointer is valid (critical!) ├─ instrument_copy_from_user() // KASAN / UBSAN detection ├─ raw_copy_from_user(...) // Actual copy (arch-specific) └─ if (res) memset(..., 0, res) // **Zero-padding!**
Zero-padding: If only part of the data is copied (e.g., due to a page fault),the remaining uncopied portion will be zeroed out. This is to prevent kernel information leaks (e.g., uninitialized memory on the stack being read by users).
access_ok(): Ensure thatfrompoints to user space and the length is valid (prevent access to kernel addresses)
check_object_size(): Prevent buffer overflow (in conjunction with FORTIFY_SOURCE)
instrument_*: KASAN detects out-of-bounds access
Even if the copy fails, it will not panic; instead, it returns the number of uncopied bytes, which the driver can handle accordingly.
copy_to_user()
Function prototype:
1 2 3 4 5 6 7
static __always_inline unsignedlong __must_check copy_to_user(void __user *to, constvoid *from, unsignedlong n) { if (likely(check_copy_size(from, n, true))) n = _copy_to_user(to, from, n); return n; }
Function purpose: Copy data from kernel space to user space.
Parameter meanings:
*tois a pointer to user space
fromis a pointer to kernel space
nis the number of bytes to copy from kernel space to user space
/** * copy_struct_from_user: copy a struct from userspace * @dst: Destination address, in kernel space. This buffer must be @ksize * bytes long. * @ksize: Size of @dst struct. * @src: Source address, in userspace. * @usize: (Alleged) size of @src struct. * * Copies a struct from userspace to kernel space, in a way that guarantees * backwards-compatibility for struct syscall arguments (as long as future * struct extensions are made such that all new fields are *appended* to the * old struct, and zeroed-out new fields have the same meaning as the old * struct). * * @ksize is just sizeof(*dst), and @usize should've been passed by userspace. * The recommended usage is something like the following: * * SYSCALL_DEFINE2(foobar, const struct foo __user *, uarg, size_t, usize) * { * int err; * struct foo karg = {}; * * if (usize > PAGE_SIZE) * return -E2BIG; * if (usize < FOO_SIZE_VER0) * return -EINVAL; * * err = copy_struct_from_user(&karg, sizeof(karg), uarg, usize); * if (err) * return err; * * // ... * } * * There are three cases to consider: * * If @usize == @ksize, then it's copied verbatim. * * If @usize < @ksize, then the userspace has passed an old struct to a * newer kernel. The rest of the trailing bytes in @dst (@ksize - @usize) * are to be zero-filled. * * If @usize > @ksize, then the userspace has passed a new struct to an * older kernel. The trailing bytes unknown to the kernel (@usize - @ksize) * are checked to ensure they are zeroed, otherwise -E2BIG is returned. * * Returns (in all cases, some data may have been copied): * * -E2BIG: (@usize > @ksize) and there are non-zero trailing bytes in @src. * * -EFAULT: access to userspace failed. */ static __always_inline __must_check int copy_struct_from_user(void *dst, size_t ksize, constvoid __user *src, size_t usize) { size_t size = min(ksize, usize); size_t rest = max(ksize, usize) - size;
/* Deal with trailing bytes. */ if (usize < ksize) { memset(dst + size, 0, rest); } elseif (usize > ksize) { int ret = check_zeroed_user(src + size, rest); if (ret <= 0) return ret ?: -E2BIG; } /* Copy the interoperable parts of the struct. */ if (copy_from_user(dst, src, size)) return -EFAULT; return0; }
Reads a simple variable, handling faults internally. The macro copies the value of a user-space variable to kernel space. Note: x is set to 0 on error.
x represents the kernel variable storing the result
ptr is the source address in user space.
The result of dereferencing ptr must be assignable to x without a cast.
Returns 0 on success, and on error returns-EFAULT。
staticvoid __exit my_driver_exit(void) { // Delete device node device_destroy(class, dev_id); // Delete the class of this device node class_destroy(class);
// Delete cdev cdev_del(&cdev_test); // Release device number unregister_chrdev_region(dev_id, 1);
MODULE_LICENSE("GPL"); MODULE_AUTHOR("even629<asqwgo@163.com>"); MODULE_DESCRIPTION("This is a test sample for file private data");
Test:
1 2 3 4 5 6 7 8 9
$ insmod file_private_data_test.ko [ 8.022277] file_private_data_test: loading out-of-tree module taints kernel. [ 8.032581] file_private_test init $ cat /dev/char_test_dev [ 14.998329] file_private_test_open is called [ 14.998580] file->private_data is set [ 14.999659] file_private_test_read is called Hello World from kernel [ 15.000469] file_private_test_release is called
Scenario for using file private data
In Linux, the major device number is used to represent a specific type of driver, while the minor device number represents individual devices under that driver.
If our driver nowneeds to support devices with the same major device number but different minor device numbers, our driver can be written using file private data.
BUILD_BUG_ON_MSG(...)Compile-time type check to ensure ptr matches the structure member type, avoiding type errors.
__mptr - offsetof(type, member)Core formula: Subtract the member’s offset within the structure from the member pointer to obtain the starting address of the structure.
offsetof(type, member)Standard macro to calculate the byte offset of a member within a structure.
MODULE_LICENSE("GPL"); MODULE_AUTHOR("even629<asqwgo@163.com>"); MODULE_DESCRIPTION("This is a test sample for container_of");
Miscellaneous device driver
Miscellaneous devices belong toa special type of character device, which is an encapsulation of character devices and is essentially a character device.
In Linux, devices that cannot be categorized are defined as miscellaneous devices. Compared to character devices, miscellaneous devices have the following two advantages:
Saving major device numbers: The major device number of miscellaneous devices is fixed at 10, so when multiple miscellaneous device drivers are registered in the system, they only need to be distinguished by minor device numbers. In contrast, character devices, whether dynamically or statically allocated, consume a major device number, leading to waste of major device numbers.
Simple to use:**The miscellaneous device driver itself includes operations for creating device nodes, eliminating the need for additional use of the class_create() function and device_create() function.**Implement automatic device node creation. Simply fill in the members of the file_operations structure in the driver.
To define a miscellaneous device, generally only need to fill in the three member variables: minor, name, and fops in the miscdevice structure.
minorThe minor device number for the miscellaneous device can be selected from the predefined minor device numbers in the kernel sourceinclude/linux/miscdevice.hfile, or you can define your own minor device number (as long as it is not used by other devices). Typically, this parameter is set toMISC_DYNAMIC_MINOR, which means the minor device number is automatically allocated.
nameIndicates the name of the miscellaneous device. That is, after the miscellaneous device driver is successfully registered, a device node named name will be generated in the /dev directory.
fopsPoints to the file_operations structure, representing the file operation set.
misc_register
1
externintmisc_register(struct miscdevice *misc);
Function purpose: Based on misc_class, construct a device, mount the miscdevice structure to the misc_list, and initialize structures related to the Linux device model. This serves to register the miscellaneous device.
Parameter meaning:
misc: Pointer to the miscellaneous device structure
Function return value: Returns 0 on success, negative on failure
The label order is ‘reverse release’, meaninglater allocated ones are released first。
PTR_ERR()+IS_ERR()is used to determineclass_create()anddevice_create()error.
PTR_ERR()Convert the error pointer to an error code and return the error code.
gotoCan avoid writing multiple times.ifCheck and clean up the code to make it concise.
IS_ERR()
If a function returns a pointer type, it will return a NULL pointer in case of an error, but returning NULL does not reveal the exact nature of the problem. Some functions need to return an actual error code so that engineers can make correct judgments based on the return value. For any pointer, there are necessarily three cases:
Valid pointer
NULL (i.e., null pointer)
Error pointer (i.e., invalid pointer)
In the Linux kernel, the so-calledError pointer points to the last page of kernel space, for example, for a 64-bit system, the address of the last page is 0xfffffffffffff000~0xffffffffffffffff, The last page address is reserved and is associated with kernel-defined error codes, so error codes can be used to indicate the corresponding error condition. If a pointer points to the address range of this page, it is defined as an error pointer. In the Linux kernel source code, API functions related to error pointers are provided, mainly IS_ERR()、PTR_ERR(), ERR_PTR() functions, etc., and the source code of these functions is ininclude/linux/err.hin the file.
/* * Kernel pointers have redundant information, so we can use a * scheme where we can return either an error code or a normal * pointer with the same return value. * * This should be a per-architecture thing, to allow different * error and pointer decisions. */ #define MAX_ERRNO 4095
/** * ERR_CAST - Explicitly cast an error-valued pointer to another pointer type * @ptr: The pointer to cast. * * Explicitly cast an error-valued pointer to another pointer type in such a * way as to make it clear that's what's going on. */ staticinlinevoid * __must_check ERR_CAST(__force constvoid *ptr) { /* cast away the const */ return (void *) ptr; }
#define EPERM 1 /* Operation not permitted */ #define ENOENT 2 /* No such file or directory */ #define ESRCH 3 /* No such process */ #define EINTR 4 /* Interrupted system call */ #define EIO 5 /* I/O error */ #define ENXIO 6 /* No such device or address */ #define E2BIG 7 /* Argument list too long */ #define ENOEXEC 8 /* Exec format error */ #define EBADF 9 /* Bad file number */ #define ECHILD 10 /* No child processes */ #define EAGAIN 11 /* Try again */ #define ENOMEM 12 /* Out of memory */ #define EACCES 13 /* Permission denied */ #define EFAULT 14 /* Bad address */ #define ENOTBLK 15 /* Block device required */ #define EBUSY 16 /* Device or resource busy */ #define EEXIST 17 /* File exists */ #define EXDEV 18 /* Cross-device link */ #define ENODEV 19 /* No such device */ #define ENOTDIR 20 /* Not a directory */ #define EISDIR 21 /* Is a directory */ #define EINVAL 22 /* Invalid argument */ #define ENFILE 23 /* File table overflow */ #define EMFILE 24 /* Too many open files */ #define ENOTTY 25 /* Not a typewriter */ #define ETXTBSY 26 /* Text file busy */ #define EFBIG 27 /* File too large */ #define ENOSPC 28 /* No space left on device */ #define ESPIPE 29 /* Illegal seek */ #define EROFS 30 /* Read-only file system */ #define EMLINK 31 /* Too many links */ #define EPIPE 32 /* Broken pipe */ #define EDOM 33 /* Math argument out of domain of func */ #define ERANGE 34 /* Math result not representable */
Example:
1 2 3 4
if (IS_ERR(dev1.class)) { ret = PTR_ERR(dev1.class); goto fail_class_create; }
Errors sometimes cross kernel space and propagate to user space. If the returned error is a response to a system call (open, read, ioctl, mmap), the value is automatically assigned to the user-space errno global variable, on which callingstrerror(errno)can convert the error to a readable string:
1 2 3 4 5 6
#include<errno.h>/* Access errno global variable */ #include<string.h>
If the function name is an action or imperative command, the error code returned by the function should be an integer;
If the function name is a predicate, the function should return a boolean value indicating success.
For example, add_work is a command, add_the work() function returns 0 for success and -EBUSY for failure.
Similarly, PCI device present is a predicate, pci_dev_the present() function returns 1 if a matching device is successfully found; otherwise, it returns 0.
RK3568 Lighting Up the LED
Schematic
The topeet RK3568 Working LED schematic is as follows
topeet RK3568 Working LED schematic
It can be seen that the LED is connected toGPIO0_B7this GPIO pin. When GPIO0_B7 is at a high level, transistor Q16 conducts, and LED9 lights up. When GPIO0_B7 is at a low level, transistor Q16 is cut off, and LED9 does not light up.
Query register address
GPIO0B pin multiplexing
First, we need to set the pin multiplexing of GPIO0B, which can be found from PMU_GRF
GPIO0B multiplexing register address offset
Searching for GPIO0B7 shows that its corresponding register isPMU_GRF_GPIO0B_IOMUX_Hthe setting of bits 14:12
GPIOB7 pin multiplexing
The PMU_GRF register address can be seen below as 0xFDC20000
PMU_GRF
So the multiplexing register address = base address + offset address = 0xFDC20000 + 0xC = 0xFDC2000C.
Use the io command to view the address of this register:
1
io -r -4 0xFDC2000C
GPIO register
From the Address Mapping in the TRM, it can be seen that the GPIO0 address is 0xFDD60000
Address Mapping
Introduction to the GPIO chapter in the RK3568 TRM:
3568 TRM
It can be seen that the RK3568 registers are divided into:
Direction register (DDR)
GPIO_SWPORT_DR_L
GPIO_SWPORT_DR_H
Data Register (DR)
GPIO_SWPORT_DDR_L
GPIO_SWPORT_DDR_H
External Input Register (EXT_PORT)
GPIO_EXT_PORT
namely
GPIO_SWPORT_DDR_L/Hdetermineswhether the pin is input or output
GPIO_SWPORT_DR_L/Hdeterminesthe output level when the pin is output
GPIO_EXT_PORTreflectsthe current actual level of the pin (read-only)
Data registers and direction registers are divided into L and H
GPIOA and GPIOB addresses fall on L
GPIOC and GPIOD addresses fall on H
Also:
five GPIOs's register group have five different base addresses
It can be seen that the registers of the five groups GPIO0~GPIO4 are different.
We need to first set the GPIO direction register to output, then set the output level to high.
Register offset address
GPIO_EXT_PORT
GPIO has four groups: GPIOA, GPIOB, GPIOC, GPIOD. Each group is further distinguished by numbers A0~A7, B0~B7, C0~C7, D0~D7.
GPIO0B7 is on L, so the offset address of the direction register is 0x0008, and the data register offset is 0x0000.
The base address of GPIO0 is 0xFDD60000. Therefore,
Direction register address = base address + offset address = 0xFDD60000 + 0x0008 = 0xFDD60008
Data register address = base address + offset address = 0xFDD60000 + 0x0000 = 0xFDD60000
if (size != sizeof(u32)) { pr_err("write need 4 bytes\n"); return -ENOMEM; }
if (copy_from_user(&val, buf, sizeof(u32)) != 0) { return -EFAULT; } if (val > 0) { // Configure as GPIO Output val = readl(drv_data->gpio0_swport_ddr_l); val |= 0x80008000; writel(val, drv_data->gpio0_swport_ddr_l); // Open val = readl(drv_data->gpio0_swport_dr_l); val |= 0x80008000; writel(val, drv_data->gpio0_swport_dr_l); } elseif (val == 0) { // Configure as GPIO Output val = readl(drv_data->gpio0_swport_ddr_l); val |= 0x80008000; writel(val, drv_data->gpio0_swport_ddr_l); // Close val = readl(drv_data->gpio0_swport_dr_l); val |= 0x80000000; val &= 0xffff7fff; writel(val, drv_data->gpio0_swport_dr_l); }
// Set Pin Multiplexing to GPIO val = readl(led_drv_data->gpio0b_iomux); val |= 0x70000000; // write access val &= 0xFFFF8FFF; // gpio0_b7 writel(val, led_drv_data->gpio0b_iomux);