Skip to content
Digital Rhyme
Embedded Linux board with oscilloscope probe and driver architecture flow blocks

drhyme_sensor.c Source

Platform driver, probe/remove, modprobe, misc devices, ioctl ABI, and user-space testing.

1// SPDX-License-Identifier: GPL-2.0
2/*
3 * drhyme_sensor.c - serious-but-small Linux platform driver example.
4 *
5 * The driver demonstrates:
6 * - module loading and platform_driver registration
7 * - probe/remove lifecycle
8 * - per-device state
9 * - misc character device creation
10 * - read/open/release file operations
11 * - ioctl ABI using _IO/_IOR/_IOW
12 * - Device Tree and platform modalias metadata
13 *
14 * For lab use, create_demo_device=1 registers a synthetic platform_device
15 * from this module. Real hardware normally appears from Device Tree, ACPI,
16 * PCI, USB, or board/platform code instead.
17 */
18
19#include <linux/atomic.h>
20#include <linux/compat.h>
21#include <linux/device.h>
22#include <linux/fs.h>
23#include <linux/init.h>
24#include <linux/kernel.h>
25#include <linux/miscdevice.h>
26#include <linux/module.h>
27#include <linux/mutex.h>
28#include <linux/of.h>
29#include <linux/platform_device.h>
30#include <linux/property.h>
31#include <linux/slab.h>
32#include <linux/uaccess.h>
33
34#include "drhyme_sensor_uapi.h"
35
36#define DRHYME_DRIVER_NAME "drhyme-sensor"
37#define DRHYME_ABI_VERSION 1
38
39struct drhyme_sensor {
40 struct device *dev;
41 struct miscdevice miscdev;
42 struct mutex lock;
43 atomic64_t read_count;
44 u32 config;
45 bool enabled;
46 char misc_name[32];
47};
48
49static bool create_demo_device = true;
50module_param(create_demo_device, bool, 0444);
51MODULE_PARM_DESC(create_demo_device,
52 "register a synthetic platform_device for lab testing");
53
54static struct platform_device *demo_pdev;
55
56static int drhyme_open(struct inode *inode, struct file *file)
57{
58 struct miscdevice *miscdev = file->private_data;
59 struct drhyme_sensor *sensor;
60
61 sensor = container_of(miscdev, struct drhyme_sensor, miscdev);
62 file->private_data = sensor;
63
64 dev_dbg(sensor->dev, "open\n");
65 return 0;
66}
67
68static int drhyme_release(struct inode *inode, struct file *file)
69{
70 struct drhyme_sensor *sensor = file->private_data;
71
72 dev_dbg(sensor->dev, "release\n");
73 return 0;
74}
75
76static ssize_t drhyme_read(struct file *file, char __user *buf,
77 size_t count, loff_t *ppos)
78{
79 struct drhyme_sensor *sensor = file->private_data;
80 char tmp[96];
81 int len;
82 u32 config;
83 bool enabled;
84 u64 reads;
85
86 mutex_lock(&sensor->lock);
87 config = sensor->config;
88 enabled = sensor->enabled;
89 reads = atomic64_inc_return(&sensor->read_count);
90 mutex_unlock(&sensor->lock);
91
92 len = scnprintf(tmp, sizeof(tmp),
93 "enabled=%u config=%u read_count=%llu\n",
94 enabled, config, reads);
95
96 return simple_read_from_buffer(buf, count, ppos, tmp, len);
97}
98
99static long drhyme_ioctl(struct file *file, unsigned int cmd,
100 unsigned long arg)
101{
102 struct drhyme_sensor *sensor = file->private_data;
103 struct drhyme_sensor_status status;
104 struct drhyme_sensor_config config;
105
106 if (_IOC_TYPE(cmd) != DRHYME_IOCTL_MAGIC)
107 return -ENOTTY;
108
109 switch (cmd) {
110 case DRHYME_IOCTL_RESET:
111 mutex_lock(&sensor->lock);
112 sensor->enabled = false;
113 sensor->config = 0;
114 atomic64_set(&sensor->read_count, 0);
115 mutex_unlock(&sensor->lock);
116 return 0;
117
118 case DRHYME_IOCTL_GET_STATUS:
119 memset(&status, 0, sizeof(status));
120
121 mutex_lock(&sensor->lock);
122 status.abi_version = DRHYME_ABI_VERSION;
123 status.enabled = sensor->enabled;
124 status.config = sensor->config;
125 status.read_count = atomic64_read(&sensor->read_count);
126 mutex_unlock(&sensor->lock);
127
128 if (copy_to_user((void __user *)arg, &status, sizeof(status)))
129 return -EFAULT;
130
131 return 0;
132
133 case DRHYME_IOCTL_SET_CONFIG:
134 if (copy_from_user(&config, (void __user *)arg,
135 sizeof(config)))
136 return -EFAULT;
137
138 mutex_lock(&sensor->lock);
139 sensor->config = config.config;
140 sensor->enabled = true;
141 mutex_unlock(&sensor->lock);
142 return 0;
143
144 default:
145 return -ENOTTY;
146 }
147}
148
149static const struct file_operations drhyme_fops = {
150 .owner = THIS_MODULE,
151 .open = drhyme_open,
152 .read = drhyme_read,
153 .unlocked_ioctl = drhyme_ioctl,
154#ifdef CONFIG_COMPAT
155 .compat_ioctl = compat_ptr_ioctl,
156#endif
157 .release = drhyme_release,
158 .llseek = no_llseek,
159};
160
161static int drhyme_probe(struct platform_device *pdev)
162{
163 struct drhyme_sensor *sensor;
164 u32 default_config = 0;
165 int ret;
166
167 sensor = devm_kzalloc(&pdev->dev, sizeof(*sensor), GFP_KERNEL);
168 if (!sensor)
169 return -ENOMEM;
170
171 sensor->dev = &pdev->dev;
172 mutex_init(&sensor->lock);
173 atomic64_set(&sensor->read_count, 0);
174
175 device_property_read_u32(&pdev->dev, "digital-rhyme,default-config",
176 &default_config);
177 sensor->config = default_config;
178 sensor->enabled = default_config != 0;
179
180 snprintf(sensor->misc_name, sizeof(sensor->misc_name),
181 "drhyme_sensor%d", pdev->id < 0 ? 0 : pdev->id);
182
183 sensor->miscdev.minor = MISC_DYNAMIC_MINOR;
184 sensor->miscdev.name = sensor->misc_name;
185 sensor->miscdev.fops = &drhyme_fops;
186 sensor->miscdev.parent = &pdev->dev;
187
188 ret = misc_register(&sensor->miscdev);
189 if (ret)
190 return dev_err_probe(&pdev->dev, ret,
191 "failed to register misc device\n");
192
193 platform_set_drvdata(pdev, sensor);
194
195 dev_info(&pdev->dev, "probed; userspace node is /dev/%s\n",
196 sensor->misc_name);
197 return 0;
198}
199
200static int drhyme_remove(struct platform_device *pdev)
201{
202 struct drhyme_sensor *sensor = platform_get_drvdata(pdev);
203
204 misc_deregister(&sensor->miscdev);
205 dev_info(&pdev->dev, "removed\n");
206 return 0;
207}
208
209static const struct of_device_id drhyme_of_match[] = {
210 { .compatible = "digital-rhyme,drhyme-sensor" },
211 { }
212};
213MODULE_DEVICE_TABLE(of, drhyme_of_match);
214
215static const struct platform_device_id drhyme_platform_ids[] = {
216 { .name = DRHYME_DRIVER_NAME },
217 { }
218};
219MODULE_DEVICE_TABLE(platform, drhyme_platform_ids);
220
221static struct platform_driver drhyme_driver = {
222 .probe = drhyme_probe,
223 .remove = drhyme_remove,
224 .id_table = drhyme_platform_ids,
225 .driver = {
226 .name = DRHYME_DRIVER_NAME,
227 .of_match_table = drhyme_of_match,
228 },
229};
230
231static int __init drhyme_init(void)
232{
233 int ret;
234
235 ret = platform_driver_register(&drhyme_driver);
236 if (ret)
237 return ret;
238
239 if (create_demo_device) {
240 demo_pdev = platform_device_register_simple(DRHYME_DRIVER_NAME,
241 PLATFORM_DEVID_AUTO,
242 NULL, 0);
243 if (IS_ERR(demo_pdev)) {
244 ret = PTR_ERR(demo_pdev);
245 platform_driver_unregister(&drhyme_driver);
246 return ret;
247 }
248 }
249
250 pr_info(DRHYME_DRIVER_NAME ": module loaded\n");
251 return 0;
252}
253
254static void __exit drhyme_exit(void)
255{
256 if (demo_pdev)
257 platform_device_unregister(demo_pdev);
258
259 platform_driver_unregister(&drhyme_driver);
260 pr_info(DRHYME_DRIVER_NAME ": module unloaded\n");
261}
262
263module_init(drhyme_init);
264module_exit(drhyme_exit);
265
266MODULE_LICENSE("GPL");
267MODULE_AUTHOR("Digital Rhyme Knowledge Base");
268MODULE_DESCRIPTION("Platform driver example with miscdevice and ioctl ABI");