Home | Projects | Notes > Linux Device Drivers > Exercise 1-2: Pseudo Character Driver (Single Device) - Implementation
Initialize the device or make device respond to subsequent system calls such as read() and write().
Detect device initialization errors
Check open permission (O_RDONLY, O_WRONLY, O_RDWR)
Identify the opened device using the minor number
Prepare device private data structure if required
Update f_pos if required
open method is optional. If not provided, open will always succeed and driver is not notified.
Implementation:
xxxxxxxxxx131/**2 * pcd_open()3 * Desc.    : Handles the open() system call from the user space4 * Param.   : @inode - pointer to inode object5 *            @filp - pointer to file object6 * Returns  : 0 on success, negative error code otherwise7 * Note     : N/A8 */9int pcd_open(struct inode *inode, struct file *filp)10{11    pr_info("open was successful\n");12    return 0;13}close(fd) system call from the user space)Does the reverse operations of open method. Simply put, release method should put the device in its default state, i.e., the state before the open method was called.
e.g., If open method brings the device out of low power mode, then release method may send the device back to the low power mode.
Free any data structures allocated by the open method.
Returns 0 on success, negative error code otherwise (e.g., the device does not respond when you try to de-initialize the device).
Implementation:
xxxxxxxxxx151/**2 * pcd_release()3 * Desc.    : Handles the close() system call from the user space4 * Param.   : @inode - pointer to inode struct5 *            @filp - pointer to file struct6 * Returns  : 0 on success, negative error code otherwise7 * Note     : VFS releases the file object. Called when the last reference to an8 *            open file is closed (i.e., when the f_count field of the file object9 *            becomes 0.10 */11int pcd_release(struct inode *inode, struct file *filp)12{13    pr_info("close was successful\n");14    return 0;15}read(fd, buff, 20) system call from the user space)

Read count bytes from a device starting at position f_pos.
Update the f_pos by adding the number bytes successfully read.
A return value less than count does not mean that an error has occurred.
f_op and f_pos

Data copying (kernel space 
xxxxxxxxxx151/**2 * copy_to_user()3 * Desc.    : Copies data from kernel space to user space (during read operation)4 * Param.   : @to - destination address in user space5 *            @from - source address in kernel space6 *            @n - number of bytes to copy7 * Returns  : 0 on success, number of bytes that could not be copied otherwise8 * Notes    : It checks whether the user space pointer @to is valid or not. 9 *            If the pointer is invalid, copy will not be performed. If an invalid address10 *            is encountered during the copy, only part of the data is copied. In either case,11 *            the return value is the amount of memory left to be copied.12 *            If this function returns a non-zero value, you should assume that there was13 *            a problem during the data copy.14 */15unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);If
copy_to_user()returns a non-zero value, your read function should return an appropriate error code (-EFAULT).
Implementation:
Check the user requested count value against DEV_MEM_SIZE of the device.
If f_pos + count > DEV_MEM_SIZE, then adjust the count (count = DEV_MEM_SIZE - f_pos).
Copy count number of bytes from device memory to user buffer.
Update f_pos
Return number of bytes successfully read or error code
If f_pos is at EOF, then return 0.
xxxxxxxxxx391/**2 * pcd_read()3 * Desc.    : Handles the read() system call from the user space4 * Param.   : @filp - pointer to file object5 *            @buff - pointer to user buffer6 *            @count - read count given by the user7 *            @f_pos - pointer to current file position from which the read has to begin8 * Returns  : The number of bytes read on success,9 *            0 if there is no bytes to read (EOF),10 *            appropriate error code (negative value) otherwise11 * Note     : Reads a device file @count byte(s) of data from @f_pos, returns the data back to12 *            @buff (user), and updates @f_pos.13 */14ssize_t pcd_read(struct file *filp, char __user *buff, size_t count, loff_t *f_pos)15{16    pr_info("Read requested for %zu bytes\n", count);17    pr_info("Current file position = %lld\n", *f_pos);18
19    /* Adjust the count */20    if ((*(f_pos) + count) > DEV_MEM_SIZE)21        count = DEV_MEM_SIZE - *f_pos;22
23    /* Copy to user buffer 24       Note: Global data access should be synchronized by using mutual exclusion to avoid25       race condition. */26    if (copy_to_user(buff, &device_buffer[*f_pos], count))27    {28        return -EFAULT;29    }30
31    /* Update the current file position f_pos */32    *f_pos += count;33
34    pr_info("%zu bytes successfully read\n", count);35    pr_info("Updated file position = %lld\n", *f_pos);36
37    /* Return the number of bytes successfully read */38    return count;39}
__user
Optional macro that alerts the programmer that this is a user level pointer so cannot be trusted for direct dereferencing.
A macro used with user level pointers which tells the developer not to trust or assume it as a valid pointer to avoid kernel faults.
Never try to dereference user given pointers directly in kernel level programming. Instead, use dedicated kernel functions such as
copy_to_userandcopy_from_user.
GCC doesn't care whether you use
__usermacro with user level pointer or not. This is checked bysparse, a semantic checker tool of Linux kernel to find possible coding faults.
write(fd, buff, 20))

Write count bytes into the device starting at position f_pos.
Update the f_pos by adding the number of bytes successfully written
Data copying (user space 
xxxxxxxxxx151/**2 * copy_from_user()3 * Desc.    : Copies data from user space to kernel space (during write operation)4 * Param.   : @to - destination address in user space5 *            @from - source address in kernel space6 *            @n - number of bytes to copy7 * Returns  : 0 on success, number of bytes that could not be copied otherwise8 * Notes    : It checks whether the user space pointer @to is valid or not. 9 *            If the pointer is invalid, copy will not be performed. If an invalid address10 *            is encountered during the copy, only part of the data is copied. In either case,11 *            the return value is the amount of memory left to be copied.12 *            If this function returns a non-zero value, you should assume that there was13 *            a problem during the data copy.14 */15unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);If
copy_from_user()returns a non-zero value, your write function should return an appropriate error code (-EFAULT).
Implementation:
Check the user requested count value against DEV_MEM_SIZE of the device
If f_pos + count > DEV_MEM_SIZE, then adjust the count (count = DEV_MEM_SIZE - f_pos).
Copy count number of bytes from user buffer to device memory
Update f_pos
Return the number of bytes successfully written or error code
xxxxxxxxxx441/**2 * pcd_write()3 * Desc.    : Handles the write() system call from the user space4 * Param.   : @filp - pointer to file object5 *            @buff - pointer to user buffer6 *            @count - read count given by the user7 *            @f_pos - pointer to current file position from which the read has to begin8 * Returns  : The number of bytes written on success,9 *            appropriate error code (negative value) otherwise10 * Note     : Writes a device file @count byte(s) of data from @f_pos, returns the data back to11 *            @buff (user) and updates @f_pos.12 */13ssize_t pcd_write(struct file *filp, const char __user *buff, size_t count, loff_t *f_pos)14{15    pr_info("Write requested for %zu bytes\n", count);16    pr_info("Current file position = %lld\n", *f_pos);17
18    /* Adjust the count */19    if ((*(f_pos) + count) > DEV_MEM_SIZE)20        count = DEV_MEM_SIZE - *f_pos;21
22    if (!count)23    {24        pr_err("No more space left on the device\n");25        return -ENOMEM;26    }27
28    /* Copy from user buffer 29       Note: Global data access should be synchronized by using mutual exclusion to avoid30       race condition. */31    if (copy_from_user(&device_buffer[*f_pos], buff, count))32    {33        return -EFAULT;34    }35
36    /* Update the current file position f_pos */37    *f_pos += count;38
39    pr_info("%zu bytes successfully written\n", count);40    pr_info("Updated file position = %lld\n", *f_pos);41
42    /* Return the number of bytes successfully written */43    return count;44}llseek(fd, buff, 20))Used to alter the f_pos.
If whence = SEEK_SET, filp->f_pos = off
If whence = SEEK_CUR, filp->f_pos = filp->f_pos + off
If whence = SEEK_END, filp->f_pos = DEV_MEM_SIZE + off
Implementation:
xxxxxxxxxx471/**2 * pcd_lseek()3 * Desc.    : Handles the llseek() system call4 * Param.   : @filp - pointer to file object5 *            @offset - offset value6 *            @whence - origin7 *              - SEEK_SET: The file offset is set to @offset bytes8 *              - SEEK_CUR: The file offset is set to its current location plus @offset bytes9 *              - SEEK_END: The file offset is set to the size of the file plus @offset bytes10 * Returns  : Newly updated file position on sucess, error code other wise11 * Note     : Updates the file pointer by using @offset and @whence information.12 *            Allows the file offset to be set beyond the end of the file (but this does not13 *            change the size of the file).14 */15loff_t pcd_lseek(struct file *filp, loff_t offset, int whence)16{17    loff_t temp;18
19    pr_info("lseek requested\n");20    pr_info("Current file position = %lld\n", filp->f_pos);21
22    switch (whence)23    {   24        case SEEK_SET:25            if ((offset > DEV_MEM_SIZE) || (offset < 0)) 26                return -EINVAL;27            filp->f_pos = offset;28            break;29        case SEEK_CUR:30            temp = filp->f_pos + offset;31            if ((temp > DEV_MEM_SIZE) || (temp < 0)) 32                return -EINVAL;33            filp->f_pos = temp;34            break;35        case SEEK_END:36            temp = DEV_MEM_SIZE + offset;37            if ((temp > DEV_MEM_SIZE) || (temp < 0)) 38                return -EINVAL;39            filp->f_pos = temp;40            break;41        default:42            return -EINVAL;43    }   44
45    pr_info("Updated file position = %lld\n", filp->f_pos);46    return filp->f_pos;47}
xxxxxxxxxx3011/**2 * Filename     : pcd.c3 * Description  : A pseudo character deriver4 * Author       : Kyungjae Lee5 * Created      : 05/19/20236 * Updates      : 06/01/2023 - Complete file operation methods implementation7 *                           - Add error handling features8 */9
10/* class_create(), device_create() */14/* MAJOR(), MINOR() */15
17/* For debugging purpose, prefix current function name in front of the string printed by pr_info()  */20
21/* Global variables */22char device_buffer[DEV_MEM_SIZE];   /* Pseudo character device's buffer */23dev_t device_number;                /* Stores the device number */24struct cdev pcd_cdev;               /* Cdev variable */25
26/**27 * Device driver specific file operation methods that handle system calls28 */29
30/**31 * pcd_lseek()32 * Desc.    : Handles the llseek() system call33 * Param.   : @filp - pointer to file object34 *            @offset - offset value35 *            @whence - origin36 *              - SEEK_SET: The file offset is set to @offset bytes37 *              - SEEK_CUR: The file offset is set to its current location plus @offset bytes38 *              - SEEK_END: The file offset is set to the size of the file plus @offset bytes39 * Returns  : Newly updated file position on sucess, error code other wise40 * Note     : Updates the file pointer by using @offset and @whence information.41 *            Allows the file offset to be set beyond the end of the file (but this does not42 *            change the size of the file).43 */44loff_t pcd_lseek(struct file *filp, loff_t offset, int whence)45{46    loff_t temp;47
48    pr_info("lseek requested\n");49    pr_info("Current file position = %lld\n", filp->f_pos);50
51    switch (whence)52    {53        case SEEK_SET:54            if ((offset > DEV_MEM_SIZE) || (offset < 0))55                return -EINVAL;56            filp->f_pos = offset;57            break;58        case SEEK_CUR:59            temp = filp->f_pos + offset;60            if ((temp > DEV_MEM_SIZE) || (temp < 0))61                return -EINVAL;62            filp->f_pos = temp;63            break;64        case SEEK_END:65            temp = DEV_MEM_SIZE + offset;66            if ((temp > DEV_MEM_SIZE) || (temp < 0))67                return -EINVAL;68            filp->f_pos = temp;69            break;70        default:71            return -EINVAL;72    }73
74    pr_info("Updated file position = %lld\n", filp->f_pos);75    return filp->f_pos;76}77
78/**79 * pcd_read()80 * Desc.    : Handles the read() system call from the user space81 * Param.   : @filp - pointer to file object82 *            @buff - pointer to user buffer83 *            @count - read count given by the user84 *            @f_pos - pointer to current file position from which the read has to begin85 * Returns  : The number of bytes read on success,86 *            0 if there is no bytes to read (EOF),87 *            appropriate error code (negative value) otherwise88 * Note     : Reads a device file @count byte(s) of data from @f_pos, returns the data back to89 *            @buff (user), and updates @f_pos.90 */91ssize_t pcd_read(struct file *filp, char __user *buff, size_t count, loff_t *f_pos)92{93    pr_info("Read requested for %zu bytes\n", count);94    pr_info("Current file position = %lld\n", *f_pos);95
96    /* Adjust the count */97    if ((*(f_pos) + count) > DEV_MEM_SIZE)98        count = DEV_MEM_SIZE - *f_pos;99
100    /* Copy to user buffer 101       Note: Global data access should be synchronized by using mutual exclusion to avoid102       race condition. */103    if (copy_to_user(buff, &device_buffer[*f_pos], count))104    {105        return -EFAULT;106    }107
108    /* Update the current file position f_pos */109    *f_pos += count;110
111    pr_info("%zu bytes successfully read\n", count);112    pr_info("Updated file position = %lld\n", *f_pos);113
114    /* Return the number of bytes successfully read */115    return count;116}117
118/**119 * pcd_write()120 * Desc.    : Handles the write() system call from the user space121 * Param.   : @filp - pointer to file object122 *            @buff - pointer to user buffer123 *            @count - read count given by the user124 *            @f_pos - pointer to current file position from which the read has to begin125 * Returns  : The number of bytes written on success,126 *            appropriate error code (negative value) otherwise127 * Note     : Writes a device file @count byte(s) of data from @f_pos, returns the data back to128 *            @buff (user) and updates @f_pos.129 */130ssize_t pcd_write(struct file *filp, const char __user *buff, size_t count, loff_t *f_pos)131{132    pr_info("Write requested for %zu bytes\n", count);133    pr_info("Current file position = %lld\n", *f_pos);134
135    /* Adjust the count */136    if ((*(f_pos) + count) > DEV_MEM_SIZE)137        count = DEV_MEM_SIZE - *f_pos;138
139    if (!count)140    {141        pr_err("No more space left on the device\n");142        return -ENOMEM;143    }144
145    /* Copy from user buffer 146       Note: Global data access should be synchronized by using mutual exclusion to avoid147       race condition. */148    if (copy_from_user(&device_buffer[*f_pos], buff, count))149    {150        return -EFAULT;151    }152
153    /* Update the current file position f_pos */154    *f_pos += count;155
156    pr_info("%zu bytes successfully written\n", count);157    pr_info("Updated file position = %lld\n", *f_pos);158
159    /* Return the number of bytes successfully written */160    return count;161}162
163/**164 * pcd_open()165 * Desc.    : Handles the open() system call from the user space166 * Param.   : @inode - pointer to inode object167 *            @filp - pointer to file object168 * Returns  : 0 on success, negative error code otherwise169 * Note     : N/A170 */171int pcd_open(struct inode *inode, struct file *filp)172{173    pr_info("open was successful\n");174
175    return 0;176}177
178/**179 * pcd_release()180 * Desc.    : Handles the close() system call from the user space181 * Param.   : @inode - pointer to inode object182 *            @filp - pointer to file object183 * Returns  : 0 on success, negative error code otherwise184 * Note     : VFS releases the file object. Called when the last reference to an185 *            open file is closed (i.e., when the f_count field of the file object186 *            becomes 0.187 */188int pcd_release(struct inode *inode, struct file *filp)189{190    pr_info("release was successful\n");191
192    return 0;193}194
195/* File operations of the driver */196struct file_operations pcd_fops = { 197    /* This struct member init method is supported from C99 on */198    .open = pcd_open,199    .write = pcd_write,200    .read = pcd_read,201    .llseek = pcd_lseek,202    .release = pcd_release,203    .owner = THIS_MODULE204};205
206struct class *class_pcd;207struct device *device_pcd;208
209/* Module initialization entry point */210static int __init pcd_driver_init(void)211{212    int ret;213
214    /* 1. Dynamically allocate a device number */215    ret = alloc_chrdev_region(&device_number, 0, 1, "pcd_devices"); /* fs/char_dev.c */216    /* Handle error */217    if (ret < 0)218    {219        pr_err("Device number allocation (alloc_chrdev_region) failed\n");220        goto err_alloc_chrdev_region;221    }222
223    pr_info("Device number <major>:<minor> = %d:%d\n",MAJOR(device_number), MINOR(device_number));224        /* Print the function name for debugging purpose */225
226    /* 2. Initialize the cdev structure with fops */227    cdev_init(&pcd_cdev, &pcd_fops);228
229    /* 3. Register a device (cdev structure) with VFS */230    pcd_cdev.owner = THIS_MODULE;231    ret = cdev_add(&pcd_cdev, device_number, 1);    /* Register one device */232    /* Handle error */233    if (ret < 0)234    {235        pr_err("Device registration (cdev_add) failed\n");236        goto err_cdev_add;237    }238
239    /* 4. Create device class under /sys/class/ */240    class_pcd = class_create(THIS_MODULE, "pcd_class"); /* Returns a pointer to class struct */241    if (IS_ERR(class_pcd))242    {243        pr_err("Class creation (class_create) failed\n");244        ret = PTR_ERR(class_pcd);245            /* PTR_ERR() converts pointer to error code (int)246               ERR_PTR() converts error code (int) to pointer */247        goto err_class_create;248    }249
250    /* 5. Populate the sysfs with device information */251    device_pcd = device_create(class_pcd, NULL, device_number, NULL, "pcd");252        /* Second arg is NULL since there's no hierarchy at this point */253        /* "pdc" will appear under /dev/ */254    if (IS_ERR(device_pcd))255    {256        pr_err("Device creation (device_create) failed\n");257        ret = PTR_ERR(device_pcd);258        goto err_device_create;259    }260
261    pr_info("Module init was successful\n");262
263    return 0;264
265/* Handle when device_create() function fails */266err_device_create:267    class_destroy(class_pcd);268
269/* Handle when class_create() function fails */270err_class_create:271    cdev_del(&pcd_cdev);272
273/* Handle when cdev_add() function fails */274err_cdev_add:275    unregister_chrdev_region(device_number, 1);276
277/* Handle when alloc_chrdev_region() function fails */278err_alloc_chrdev_region:279    pr_info("Module insertion failed\n");280    return ret; /* returns the error code */281}282
283/* Module cleanup entry point */284static void __exit pcd_driver_cleanup(void)285{286    device_destroy(class_pcd, device_number);287    class_destroy(class_pcd);288    cdev_del(&pcd_cdev);289    unregister_chrdev_region(device_number, 1);290    pr_info("module unloaded\n");291}292
293/* Registration */294module_init(pcd_driver_init);295module_exit(pcd_driver_cleanup);296
297/* Module description */298MODULE_LICENSE("GPL");  /* This module adheres to the GPL licensing */299MODULE_AUTHOR("Kyungjae Lee");300MODULE_DESCRIPTION("A pseudo character driver");301MODULE_INFO(board, "BeagleBone Black REV A5");