54. Capstone: one sensor, driven two ways
Take the LM75-style I2C temperature sensor from Section 48.2 and build it both ways from Section 47's table, so the trade-off is concrete rather than theoretical.
Version A — user space (what you'd actually ship for this task):
exactly Section 48.2's read_temperature, wrapped in a small CLI
(Section 24) that prints a reading every second:
#include <stdio.h>
#include <unistd.h>
int read_temperature(const char *bus_path); // from Section 48.2
int main(void) {
while (1) {
int c = read_temperature("/dev/i2c-1");
if (c != -1) printf("%d C\n", c);
sleep(1);
}
}
No kernel code, no reboot risk, ships as an ordinary binary — this is correctly the default answer for "read a sensor" per Section 47.
Version B — a minimal kernel driver, for when the sensor's readings
need to feed a kernel subsystem directly (for example, the kernel's
hwmon framework, so /sys/class/hwmon/.../temp1_input and every
existing temperature-monitoring tool works automatically, with no
custom user-space program at all):
// lm75_mini.c — sketch: an i2c_driver that reads the same register Section 48.2 did,
// but from kernel context, matched to the hardware via the i2c core instead of a
// hand-opened /dev/i2c-1 node
#include <linux/module.h>
#include <linux/i2c.h>
static int lm75_read_temp(struct i2c_client *client) {
s32 raw = i2c_smbus_read_word_data(client, 0x00); // same register as Section 48.2
if (raw < 0) return raw;
s16 be = (s16)((raw << 8) | (raw >> 8)); // the chip returns big-endian
return (be >> 7) / 2; // identical math to Section 48.2
}
static int lm75_probe(struct i2c_client *client) {
int temp = lm75_read_temp(client);
dev_info(&client->dev, "lm75_mini: %d C\n", temp);
return 0;
}
static const struct i2c_device_id lm75_id[] = { { "lm75_mini", 0 }, { } };
MODULE_DEVICE_TABLE(i2c, lm75_id);
static struct i2c_driver lm75_driver = {
.driver = { .name = "lm75_mini" },
.probe = lm75_probe,
.id_table = lm75_id,
};
module_i2c_driver(lm75_driver); // registers the driver; the i2c core calls .probe
// automatically once a matching device is found
MODULE_LICENSE("GPL");
The line worth sitting with is .probe = lm75_probe: Version A decides
when to talk to the sensor (your while loop calls the shots).
Version B instead registers interest — module_i2c_driver hands
control to the kernel's i2c core, which calls lm75_probe on your
behalf whenever a device matching lm75_id (typically declared in the
board's Device Tree, outside the scope of this guide) actually shows
up. That inversion — you stop calling the hardware and start being
called by a framework when the hardware appears — is the core idea
behind every Linux driver model (i2c, SPI, USB, platform, PCI): each
framework differs in its match rules and callback set, but every one of
them replaces your main()-style control flow with a probe/remove
pair the kernel drives.