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.
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
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.
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.
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)
The configuration file is located at
qemu_dir/configs/devices/i386-softemu/defgault.mak
now btw