Message ID | 20190510180410.GA10349@erokenlabserver (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | [v3] chardev/char-i2c: Implement Linux I2C character device | expand |
Ernest Esene <eroken1@gmail.com> writes: > Add support for Linux I2C character device for I2C device passthrough > For example: > -chardev i2c,address=0x46,path=/dev/i2c-N,id=i2c-chardev > > QEMU supports emulation of I2C devices in software but currently can't > passthrough to real I2C devices. This feature is needed by developers > using QEMU for writing and testing software for I2C devices. > > Signed-off-by: Ernest Esene <eroken1@gmail.com> > --- > v3: > * change licence to GPLv2+ > * use non blocking IO for the chardev > * change "address" to QEMU_OPT_NUMBER > * update qemu-options.hx > --- > v2: > * Fixed errors > * update "MAINTAINERS" file. > --- > MAINTAINERS | 5 ++ > chardev/Makefile.objs | 1 + > chardev/char-linux-i2c.c | 126 +++++++++++++++++++++++++++++++++++++++++++++++ > chardev/char.c | 3 ++ > include/chardev/char.h | 1 + > qapi/char.json | 17 +++++++ > qemu-options.hx | 14 +++++- > 7 files changed, 166 insertions(+), 1 deletion(-) > create mode 100644 chardev/char-linux-i2c.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 66ddbda9c9..d834a12241 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -1801,6 +1801,11 @@ M: Samuel Thibault <samuel.thibault@ens-lyon.org> > S: Maintained > F: chardev/baum.c > > +Character Devices (I2C) > +M: Ernest Esene <eroken1@gmail.com> > +S: Maintained > +F: chardev/char-linux-i2c.c > + > Command line option argument parsing > M: Markus Armbruster <armbru@redhat.com> > S: Supported > diff --git a/chardev/Makefile.objs b/chardev/Makefile.objs > index d68e1347f9..7b64009aa6 100644 > --- a/chardev/Makefile.objs > +++ b/chardev/Makefile.objs > @@ -16,6 +16,7 @@ chardev-obj-y += char-stdio.o > chardev-obj-y += char-udp.o > chardev-obj-$(CONFIG_WIN32) += char-win.o > chardev-obj-$(CONFIG_WIN32) += char-win-stdio.o > +chardev-obj-$(CONFIG_LINUX) +=char-linux-i2c.o Space after +=, please. > > common-obj-y += msmouse.o wctablet.o testdev.o > common-obj-$(CONFIG_BRLAPI) += baum.o > diff --git a/chardev/char-linux-i2c.c b/chardev/char-linux-i2c.c > new file mode 100644 > index 0000000000..847a18f611 > --- /dev/null > +++ b/chardev/char-linux-i2c.c > @@ -0,0 +1,126 @@ > +/* > + * QEMU System Emulator > + * Linux I2C device support as a character device. > + * > + * Copyright (c) 2019 Ernest Esene <eroken1@gmail.com> > + * > + * This work is licensed under the terms of the GNU GPL, version 2 or > + * later. See the COPYING file in the top-level directory. > + */ > +#include "qemu/osdep.h" > +#include "qapi/error.h" > +#include "qemu/option.h" > +#include "qemu-common.h" > +#include "io/channel-file.h" > +#include "qemu/cutils.h" > +#include "qemu/sockets.h" > + > +#include "chardev/char-fd.h" > +#include "chardev/char.h" > + > +#include <sys/ioctl.h> > +#include <linux/i2c-dev.h> > +#include <linux/i2c.h> > + > +#define CHR_IOCTL_I2C_SET_ADDR 1 > + > +#define CHR_I2C_ADDR_10BIT_MAX 1023 > +#define CHR_I2C_ADDR_7BIT_MAX 127 > + > +static int i2c_ioctl(Chardev *chr, int cmd, void *arg) > +{ > + FDChardev *fd_chr = FD_CHARDEV(chr); > + QIOChannelFile *floc = QIO_CHANNEL_FILE(fd_chr->ioc_in); > + int fd = floc->fd; > + int addr; > + unsigned long funcs; > + > + switch (cmd) { > + case CHR_IOCTL_I2C_SET_ADDR: > + addr = (intptr_t)arg; > + > + if (addr > CHR_I2C_ADDR_7BIT_MAX) { > + if (ioctl(fd, I2C_FUNCS, &funcs) < 0) { > + goto err; > + } > + if (!(funcs & I2C_FUNC_10BIT_ADDR)) { > + goto err; > + } > + if (ioctl(fd, I2C_TENBIT, addr) < 0) { > + goto err; > + } > + } else { > + if (ioctl(fd, I2C_SLAVE, addr) < 0) { > + goto err; > + } > + } > + break; > + > + default: > + return -ENOTSUP; > + } > + return 0; > +err: > + return -ENOTSUP; > +} > + > +static void qmp_chardev_open_i2c(Chardev *chr, ChardevBackend *backend, > + bool *be_opened, Error **errp) > +{ > + ChardevI2c *i2c = backend->u.i2c.data; > + void *addr; > + int fd; > + > + fd = qmp_chardev_open_file_source(i2c->device, O_RDWR | O_NONBLOCK, errp); > + if (fd < 0) { > + return; > + } > + qemu_set_nonblock(fd); > + qemu_chr_open_fd(chr, fd, fd); > + addr = (void *)(intptr_t)i2c->address; > + i2c_ioctl(chr, CHR_IOCTL_I2C_SET_ADDR, addr); > +} > + > +static void qemu_chr_parse_i2c(QemuOpts *opts, ChardevBackend *backend, > + Error **errp) > +{ > + const char *device = qemu_opt_get(opts, "path"); > + long address = qemu_opt_get_number(opts, "address", LONG_MAX); > + ChardevI2c *i2c; > + > + if (device == NULL) { > + error_setg(errp, "chardev: i2c: no device path given"); > + return; > + } > + if (address < 0 || address > CHR_I2C_ADDR_10BIT_MAX) { > + error_setg(errp, "chardev: i2c: device address out of range"); > + return; > + } > + backend->type = CHARDEV_BACKEND_KIND_I2C; > + i2c = backend->u.i2c.data = g_new0(ChardevI2c, 1); > + qemu_chr_parse_common(opts, qapi_ChardevI2c_base(i2c)); > + i2c->device = g_strdup(device); > + i2c->address = (int16_t)address; > +} > + > +static void char_i2c_class_init(ObjectClass *oc, void *data) > +{ > + ChardevClass *cc = CHARDEV_CLASS(oc); > + > + cc->parse = qemu_chr_parse_i2c; > + cc->open = qmp_chardev_open_i2c; > + cc->chr_ioctl = i2c_ioctl; > +} > + > +static const TypeInfo char_i2c_type_info = { > + .name = TYPE_CHARDEV_I2C, > + .parent = TYPE_CHARDEV_FD, > + .class_init = char_i2c_class_init, > +}; > + > +static void register_types(void) > +{ > + type_register_static(&char_i2c_type_info); > +} > + > +type_init(register_types); > diff --git a/chardev/char.c b/chardev/char.c > index 54724a56b1..8f5ffe16e6 100644 > --- a/chardev/char.c > +++ b/chardev/char.c > @@ -926,6 +926,9 @@ QemuOptsList qemu_chardev_opts = { > },{ > .name = "logappend", > .type = QEMU_OPT_BOOL, > + },{ > + .name = "address", > + .type = QEMU_OPT_NUMBER, > }, > { /* end of list */ } > }, > diff --git a/include/chardev/char.h b/include/chardev/char.h > index c0b57f7685..0e08b70fc9 100644 > --- a/include/chardev/char.h > +++ b/include/chardev/char.h > @@ -245,6 +245,7 @@ int qemu_chr_wait_connected(Chardev *chr, Error **errp); > #define TYPE_CHARDEV_SERIAL "chardev-serial" > #define TYPE_CHARDEV_SOCKET "chardev-socket" > #define TYPE_CHARDEV_UDP "chardev-udp" > +#define TYPE_CHARDEV_I2C "chardev-i2c" > > #define CHARDEV_IS_RINGBUF(chr) \ > object_dynamic_cast(OBJECT(chr), TYPE_CHARDEV_RINGBUF) > diff --git a/qapi/char.json b/qapi/char.json > index a6e81ac7bc..7168b58cfe 100644 > --- a/qapi/char.json > +++ b/qapi/char.json > @@ -240,6 +240,22 @@ > 'data': { 'device': 'str' }, > 'base': 'ChardevCommon' } > > +## > +# @ChardevI2c: > +# > +# Configuration info for i2c chardev. > +# > +# @device: The name of the special file for the device, > +# i.e. /dev/i2c-0 on linux > +# @address: The address of the i2c device on the host. > +# > +# Since: 4.1 > +## > +{ 'struct': 'ChardevI2c', > + 'data': { 'device': 'str', > + 'address': 'int16'}, > + 'base': 'ChardevCommon' } > + > ## > # @ChardevSocket: > # > @@ -398,6 +414,7 @@ > 'data': { 'file': 'ChardevFile', > 'serial': 'ChardevHostdev', > 'parallel': 'ChardevHostdev', > + 'i2c': 'ChardevI2c', Shouldn't this be 'if': 'defined(CONFIG_LINUX)'? > 'pipe': 'ChardevHostdev', > 'socket': 'ChardevSocket', > 'udp': 'ChardevUdp', > diff --git a/qemu-options.hx b/qemu-options.hx > index 51802cbb26..435b6975dd 100644 > --- a/qemu-options.hx > +++ b/qemu-options.hx > @@ -2695,6 +2695,9 @@ DEF("chardev", HAS_ARG, QEMU_OPTION_chardev, > #if defined(CONFIG_SPICE) > "-chardev spicevmc,id=id,name=name[,debug=debug][,logfile=PATH][,logappend=on|off]\n" > "-chardev spiceport,id=id,name=name[,debug=debug][,logfile=PATH][,logappend=on|off]\n" > +#endif > +#ifdef CONFIG_LINUX > + "-chardev i2c,id=id,address=address[,path=path][,logfile=PATH][,logappend=on|off]\n" > #endif > , QEMU_ARCH_ALL > ) > @@ -2723,7 +2726,8 @@ Backend is one of: > @option{parallel}, > @option{parport}, > @option{spicevmc}, > -@option{spiceport}. > +@option{spiceport}, > +@option{i2c}. > The specific backend will determine the applicable options. > > Use @code{-chardev help} to print all available chardev backend types. > @@ -2990,6 +2994,14 @@ Connect to a spice virtual machine channel, such as vdiport. > > Connect to a spice port, allowing a Spice client to handle the traffic > identified by a name (preferably a fqdn). > + > +@item -chardev i2c,id=@var{id},address=@var{address},path=@var{path} > + > +@option{path} i2c character device (Eg: /dev/i2c-N on Linux) > + > +@option{address} address of the slave device. > + > +I2C device support as a character device. This sentence no verb :) > ETEXI > > STEXI
On Fri, May 10, 2019 at 07:04:10PM +0100, Ernest Esene wrote: > Add support for Linux I2C character device for I2C device passthrough > For example: > -chardev i2c,address=0x46,path=/dev/i2c-N,id=i2c-chardev > > QEMU supports emulation of I2C devices in software but currently can't > passthrough to real I2C devices. This feature is needed by developers > using QEMU for writing and testing software for I2C devices. > > Signed-off-by: Ernest Esene <eroken1@gmail.com> How is -chardev i2c meant to be used? Do you have code to connect this new chardev type to an emulated I2C bus? Stefan
On Wed, May 15, 2019 at 03:17:12PM +0100, Stefan Hajnoczi wrote: > On Fri, May 10, 2019 at 07:04:10PM +0100, Ernest Esene wrote: > > Add support for Linux I2C character device for I2C device passthrough > > For example: > > -chardev i2c,address=0x46,path=/dev/i2c-N,id=i2c-chardev > > > > QEMU supports emulation of I2C devices in software but currently can't > > passthrough to real I2C devices. This feature is needed by developers > > using QEMU for writing and testing software for I2C devices. > > > > Signed-off-by: Ernest Esene <eroken1@gmail.com> > > How is -chardev i2c meant to be used? Do you have code to connect this > new chardev type to an emulated I2C bus? It is meant to be connected to emulated I2C bus as you've stated, but I don't have the code yet. > > Stefan
On Thu, May 16, 2019 at 02:49:45PM +0100, Ernest Esene wrote: > On Wed, May 15, 2019 at 03:17:12PM +0100, Stefan Hajnoczi wrote: > > On Fri, May 10, 2019 at 07:04:10PM +0100, Ernest Esene wrote: > > > Add support for Linux I2C character device for I2C device passthrough > > > For example: > > > -chardev i2c,address=0x46,path=/dev/i2c-N,id=i2c-chardev > > > > > > QEMU supports emulation of I2C devices in software but currently can't > > > passthrough to real I2C devices. This feature is needed by developers > > > using QEMU for writing and testing software for I2C devices. > > > > > > Signed-off-by: Ernest Esene <eroken1@gmail.com> > > > > How is -chardev i2c meant to be used? Do you have code to connect this > > new chardev type to an emulated I2C bus? > It is meant to be connected to emulated I2C bus as you've stated, but I > don't have the code yet. I'd like to see that code first, especially if it uses the chardev ioctl to perform operations other than just read/write. That could influence the design of chardev-i2c. Please keep this patch out-of-tree unless someone has an immediate use for it. If it gets merged too early (especially if a QEMU release is made), then it's difficult to change the command-line interface if changes are required to make it work with the I2C emulation. Stefan
diff --git a/MAINTAINERS b/MAINTAINERS index 66ddbda9c9..d834a12241 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1801,6 +1801,11 @@ M: Samuel Thibault <samuel.thibault@ens-lyon.org> S: Maintained F: chardev/baum.c +Character Devices (I2C) +M: Ernest Esene <eroken1@gmail.com> +S: Maintained +F: chardev/char-linux-i2c.c + Command line option argument parsing M: Markus Armbruster <armbru@redhat.com> S: Supported diff --git a/chardev/Makefile.objs b/chardev/Makefile.objs index d68e1347f9..7b64009aa6 100644 --- a/chardev/Makefile.objs +++ b/chardev/Makefile.objs @@ -16,6 +16,7 @@ chardev-obj-y += char-stdio.o chardev-obj-y += char-udp.o chardev-obj-$(CONFIG_WIN32) += char-win.o chardev-obj-$(CONFIG_WIN32) += char-win-stdio.o +chardev-obj-$(CONFIG_LINUX) +=char-linux-i2c.o common-obj-y += msmouse.o wctablet.o testdev.o common-obj-$(CONFIG_BRLAPI) += baum.o diff --git a/chardev/char-linux-i2c.c b/chardev/char-linux-i2c.c new file mode 100644 index 0000000000..847a18f611 --- /dev/null +++ b/chardev/char-linux-i2c.c @@ -0,0 +1,126 @@ +/* + * QEMU System Emulator + * Linux I2C device support as a character device. + * + * Copyright (c) 2019 Ernest Esene <eroken1@gmail.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or + * later. See the COPYING file in the top-level directory. + */ +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/option.h" +#include "qemu-common.h" +#include "io/channel-file.h" +#include "qemu/cutils.h" +#include "qemu/sockets.h" + +#include "chardev/char-fd.h" +#include "chardev/char.h" + +#include <sys/ioctl.h> +#include <linux/i2c-dev.h> +#include <linux/i2c.h> + +#define CHR_IOCTL_I2C_SET_ADDR 1 + +#define CHR_I2C_ADDR_10BIT_MAX 1023 +#define CHR_I2C_ADDR_7BIT_MAX 127 + +static int i2c_ioctl(Chardev *chr, int cmd, void *arg) +{ + FDChardev *fd_chr = FD_CHARDEV(chr); + QIOChannelFile *floc = QIO_CHANNEL_FILE(fd_chr->ioc_in); + int fd = floc->fd; + int addr; + unsigned long funcs; + + switch (cmd) { + case CHR_IOCTL_I2C_SET_ADDR: + addr = (intptr_t)arg; + + if (addr > CHR_I2C_ADDR_7BIT_MAX) { + if (ioctl(fd, I2C_FUNCS, &funcs) < 0) { + goto err; + } + if (!(funcs & I2C_FUNC_10BIT_ADDR)) { + goto err; + } + if (ioctl(fd, I2C_TENBIT, addr) < 0) { + goto err; + } + } else { + if (ioctl(fd, I2C_SLAVE, addr) < 0) { + goto err; + } + } + break; + + default: + return -ENOTSUP; + } + return 0; +err: + return -ENOTSUP; +} + +static void qmp_chardev_open_i2c(Chardev *chr, ChardevBackend *backend, + bool *be_opened, Error **errp) +{ + ChardevI2c *i2c = backend->u.i2c.data; + void *addr; + int fd; + + fd = qmp_chardev_open_file_source(i2c->device, O_RDWR | O_NONBLOCK, errp); + if (fd < 0) { + return; + } + qemu_set_nonblock(fd); + qemu_chr_open_fd(chr, fd, fd); + addr = (void *)(intptr_t)i2c->address; + i2c_ioctl(chr, CHR_IOCTL_I2C_SET_ADDR, addr); +} + +static void qemu_chr_parse_i2c(QemuOpts *opts, ChardevBackend *backend, + Error **errp) +{ + const char *device = qemu_opt_get(opts, "path"); + long address = qemu_opt_get_number(opts, "address", LONG_MAX); + ChardevI2c *i2c; + + if (device == NULL) { + error_setg(errp, "chardev: i2c: no device path given"); + return; + } + if (address < 0 || address > CHR_I2C_ADDR_10BIT_MAX) { + error_setg(errp, "chardev: i2c: device address out of range"); + return; + } + backend->type = CHARDEV_BACKEND_KIND_I2C; + i2c = backend->u.i2c.data = g_new0(ChardevI2c, 1); + qemu_chr_parse_common(opts, qapi_ChardevI2c_base(i2c)); + i2c->device = g_strdup(device); + i2c->address = (int16_t)address; +} + +static void char_i2c_class_init(ObjectClass *oc, void *data) +{ + ChardevClass *cc = CHARDEV_CLASS(oc); + + cc->parse = qemu_chr_parse_i2c; + cc->open = qmp_chardev_open_i2c; + cc->chr_ioctl = i2c_ioctl; +} + +static const TypeInfo char_i2c_type_info = { + .name = TYPE_CHARDEV_I2C, + .parent = TYPE_CHARDEV_FD, + .class_init = char_i2c_class_init, +}; + +static void register_types(void) +{ + type_register_static(&char_i2c_type_info); +} + +type_init(register_types); diff --git a/chardev/char.c b/chardev/char.c index 54724a56b1..8f5ffe16e6 100644 --- a/chardev/char.c +++ b/chardev/char.c @@ -926,6 +926,9 @@ QemuOptsList qemu_chardev_opts = { },{ .name = "logappend", .type = QEMU_OPT_BOOL, + },{ + .name = "address", + .type = QEMU_OPT_NUMBER, }, { /* end of list */ } }, diff --git a/include/chardev/char.h b/include/chardev/char.h index c0b57f7685..0e08b70fc9 100644 --- a/include/chardev/char.h +++ b/include/chardev/char.h @@ -245,6 +245,7 @@ int qemu_chr_wait_connected(Chardev *chr, Error **errp); #define TYPE_CHARDEV_SERIAL "chardev-serial" #define TYPE_CHARDEV_SOCKET "chardev-socket" #define TYPE_CHARDEV_UDP "chardev-udp" +#define TYPE_CHARDEV_I2C "chardev-i2c" #define CHARDEV_IS_RINGBUF(chr) \ object_dynamic_cast(OBJECT(chr), TYPE_CHARDEV_RINGBUF) diff --git a/qapi/char.json b/qapi/char.json index a6e81ac7bc..7168b58cfe 100644 --- a/qapi/char.json +++ b/qapi/char.json @@ -240,6 +240,22 @@ 'data': { 'device': 'str' }, 'base': 'ChardevCommon' } +## +# @ChardevI2c: +# +# Configuration info for i2c chardev. +# +# @device: The name of the special file for the device, +# i.e. /dev/i2c-0 on linux +# @address: The address of the i2c device on the host. +# +# Since: 4.1 +## +{ 'struct': 'ChardevI2c', + 'data': { 'device': 'str', + 'address': 'int16'}, + 'base': 'ChardevCommon' } + ## # @ChardevSocket: # @@ -398,6 +414,7 @@ 'data': { 'file': 'ChardevFile', 'serial': 'ChardevHostdev', 'parallel': 'ChardevHostdev', + 'i2c': 'ChardevI2c', 'pipe': 'ChardevHostdev', 'socket': 'ChardevSocket', 'udp': 'ChardevUdp', diff --git a/qemu-options.hx b/qemu-options.hx index 51802cbb26..435b6975dd 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -2695,6 +2695,9 @@ DEF("chardev", HAS_ARG, QEMU_OPTION_chardev, #if defined(CONFIG_SPICE) "-chardev spicevmc,id=id,name=name[,debug=debug][,logfile=PATH][,logappend=on|off]\n" "-chardev spiceport,id=id,name=name[,debug=debug][,logfile=PATH][,logappend=on|off]\n" +#endif +#ifdef CONFIG_LINUX + "-chardev i2c,id=id,address=address[,path=path][,logfile=PATH][,logappend=on|off]\n" #endif , QEMU_ARCH_ALL ) @@ -2723,7 +2726,8 @@ Backend is one of: @option{parallel}, @option{parport}, @option{spicevmc}, -@option{spiceport}. +@option{spiceport}, +@option{i2c}. The specific backend will determine the applicable options. Use @code{-chardev help} to print all available chardev backend types. @@ -2990,6 +2994,14 @@ Connect to a spice virtual machine channel, such as vdiport. Connect to a spice port, allowing a Spice client to handle the traffic identified by a name (preferably a fqdn). + +@item -chardev i2c,id=@var{id},address=@var{address},path=@var{path} + +@option{path} i2c character device (Eg: /dev/i2c-N on Linux) + +@option{address} address of the slave device. + +I2C device support as a character device. ETEXI STEXI
Add support for Linux I2C character device for I2C device passthrough For example: -chardev i2c,address=0x46,path=/dev/i2c-N,id=i2c-chardev QEMU supports emulation of I2C devices in software but currently can't passthrough to real I2C devices. This feature is needed by developers using QEMU for writing and testing software for I2C devices. Signed-off-by: Ernest Esene <eroken1@gmail.com> --- v3: * change licence to GPLv2+ * use non blocking IO for the chardev * change "address" to QEMU_OPT_NUMBER * update qemu-options.hx --- v2: * Fixed errors * update "MAINTAINERS" file. --- MAINTAINERS | 5 ++ chardev/Makefile.objs | 1 + chardev/char-linux-i2c.c | 126 +++++++++++++++++++++++++++++++++++++++++++++++ chardev/char.c | 3 ++ include/chardev/char.h | 1 + qapi/char.json | 17 +++++++ qemu-options.hx | 14 +++++- 7 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 chardev/char-linux-i2c.c