Skip to content

Instantly share code, notes, and snippets.

@jonte
Last active March 30, 2024 13:07
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jonte/b4bd83a5f2e8330418b1f3322bff74f2 to your computer and use it in GitHub Desktop.
Save jonte/b4bd83a5f2e8330418b1f3322bff74f2 to your computer and use it in GitHub Desktop.
Emulating a tmp105 temperature sensor using QEMU and Linux

This will configure QEMU to expose a tmp105 temperature sensor on the i2c-0 bus on x86_64. The temterature sensor can have its temperature set from the host OS, and the temperature can be read from the Linux client OS using the lm75 kernel module.

Building QEMU

For convenience, we will be emulating an x86 system. The x86 configuations which QEMU ships with do not contain the tmp105 sensor we will be using, so first we need to enable it.

Assuming you have the qemu sources in the qemu directory:

# cd qemu
# echo "CONFIG_TMP105=y" >> default-configs/i386-softmmu.mak
./configure && make

Setting up qemu

Using a Debian 10.5 image, which I have installed into ~/Projects/qemu/virtualdebian.img, we can start up a QEMU machine emulating a tmp105 device as such:

build/qemu-system-x86_64                    \
    --enable-kvm                            \
    -hda ~/Projects/qemu/virtualdebian.img  \
    -m 1G                                   \
    -device tmp105,id=sensor,address=0x50   \
    -qmp unix:$HOME/qmp.sock,server,nowait  \
    -nic user

The two interesting lines are the following:

-device tmp105,id=sensor,address=0x50

This adds a new QEMU device, i.e. a device which QEMU will emulate. In this case, he device added is a "tmp105" - which is a temerature sensor. The source code for this device is in hw/misc/tmp105.c in the QEMU source tree.

The device is given an ID ("sensor"). This ID is used to identify the device within QEMU. For our purposed, we will use this ID when using the qmp interface for setting the temperature value of the sensor.

Lastly, the device is given an i2c address. This is the address the temperature sensor will use for identification on the i2c bus. When addressing the device from Linux, this is the address we will use.

-qmp unix:$HOME/qmp.sock,server,nowait

This opens a QEMU Machine Protocol (QMP) 1 socket, which we will use for communicating with the tmp105 sensor from the host. The socket is a UNIX domain socket, and it is placed in ~/qmp.sock.

Setting the emp105 temperature using QMP

The QEMU source tree comes with some utility scripts for working with QMP. The ones interesting in our case are:

  • qom-list - Used to enumerate QOM devices
  • qom-set - Used to set properties on QOM devices

Using qom-list, we can check that our QEMU configuration for the temperature sensor is correct:

$ scripts/qmp/qom-list -s ~/qmp.sock /machine/peripheral/sensor/
type
@parent_bus/
realized
hotplugged
hotpluggable
address
@unnamed-gpio-out[0]/
temperature

The properties reported using qom-list correspond to the properties set in tmp105.c. Some of the properties are inherited from the I2C_SLAVE class (and its parents), but the most interesting one; temperature is set directly in tmp105.c:

object_property_add(obj, "temperature", "int",
                    tmp105_get_temperature,
                    tmp105_set_temperature, NULL, NULL);

We can trigger the tmp105_set_temperature function using QMP with the qom-set command:

$ scripts/qmp/qom-set -s ~/qmp.sock sensor.temperature 0
{}

The command above will set the current temperature of the sensor to 0 degrees celcius.

Reading the temperature from Linux

We will expose the i2c bus to userspace through sysfs using the i2c-dev module. In order to load it automatically from systemd-load-modules.service we need to tell systemd to load the module automatically:

echo "i2c-dev" >> /etc/modules-load.d/i2c-dev.conf

While the above command ensures that the i2c-dev module exposes the sysfs we need, we still need to load the module for our particular temperature sensor (tmp105). tmp105 is handled by the lm75 kernel module. In order to load this module for use with our i2c device, we add the following service override:

# mkdir /etc/systemd/system/systemd-modules-load.service.override.d/
# echo "[Service]" > /etc/systemd/system/systemd-modules-load.service.override.d/10-lm75.conf
# echo "ExecStartPost=/bin/sh -c 'echo lm75 0x50 > /sys/class/i2c-adapter/i2c-0/new_device'" \
    > /etc/systemd/system/systemd-modules-load.override.d/10-lm75.conf

Writing lm75 0x50 to /sys/class/i2c-adapter/i2c-0/new_device after the i2c-dev module has been loaded causes the lm75 module to be loaded to handle the device on address 0x50, which is what we want.

The confguration can be checked using the following systemctl command:

# systemctl cat systemd-modules-load.service
    ... snip ...
    # /etc/systemd/system/systemd-modules-load.service.override.d/10-lm75.conf
    ExecStartPost=/bin/sh -c 'echo lm75 0x50 > /sys/class/i2c-adapter/i2c-0/new_device'

The configuration will take effect with the following commands:

# systemctl daemon-reload
# systemctl restart systemd-modules-load.service

We can verify that the drivers have been correctly loaded using lsmod:

# lsmod | grep "i2c_dev\|lm75"
lm75            20480   0
i2c_dev         20480   0

The lm-sensors project has support for the lm75 module. We can use this software to check the temperature readings of the sensor:

# sudo apt install lm-sensors
# sensors
lm75-i2c-0-50
Adapter: SMBus PIIX4 at 0700
temp1:         +0.0°C (high = +0.0°C, hyst = +0.0°C)

Going back to the host shell, we can change the temperature using the qom-set command:

$ scripts/qmp/qom-set -s ~/qmp.sock sensor.temperature 20000
{}

In our QEMU VM, we will now see this:

# sensors
lm75-i2c-0-50
Adapter: SMBus PIIX4 at 0700
temp1:         +20.0°C (high = +0.0°C, hyst = +0.0°C)
@upintheairsheep
Copy link

Can you pull this into mainline QEMU please?

@jonte
Copy link
Author

jonte commented Aug 25, 2023

@upintheairsheep I haven't used these instructions in a while. If I get some time over, I'll revisit them and see if there is a good place in the QEmu docs where they can be added.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment