On Sun, Jul 26, 2020 at 11:04 AM Alex Elder elder@linaro.org wrote:
On 7/24/20 7:06 AM, Vaishnav M A wrote:
Attached is a patch for the mikroBUS driver which helps to instantiate an add-on board device on a mikrobus port by fetching the device identifier manifest binary from an EEPROM on-board the device. mikroBUS is an add-on board socket standard by MikroElektronika that can be freely used by anyone following the guidelines, more details and discussions on the status of the driver can be found here in this eLinux wiki: https://elinux.org/Mikrobus
I had some other things I would comment on in this code review, but there are a lot of somewhat superficial things you should fix. I looked at much--but not all--of this, and I'll want to review this again after you've fixed the simple things. I also can provide more substantive feedback after I've had more time to digest the bigger picture of microBUS.
You should assume it will take a few iterations to get to the point where things are shaping up for acceptance.
All that said, your basic foundation looks good.
In the current state of the driver, more than 80 different add-on boards have been tested on the BeagleBoard.org PocketBeagle and the manifest binary is generated using the same manifesto tool used to generate Greybus manifest binaries, The pull request to manifesto to add new descriptors specific to mikrobus is here : https://github.com/projectara/manifesto/pull/2 The utilization of Greybus manifest binaries here is not entirely coincidental, We are evaluating ways to add mikroBUS sockets and devices via Greybus expansion.
The mikroBUS standard includes SPI, I2C, UART, PWM, ADC, GPIO and power (3.3V and 5V) connections to interface common embedded peripherals, There are more than 750 add-on boards ranging from wireless connectivity boards to human-machine interface sensors which conform to the mikroBUS standard, out of which more than 140 boards already have support in the Linux kernel.Today, there is no mainlinesolution for enabling mikroBUS add-on boards at run-time, the most straight forward method for loading drivers is to provide device-tree overlay fragments at boot time, this method suffers from the need to maintain a large out-of-tree database for which there is need to maintain an overlay for every mikroBUS add-on board for every Linux system and for every mikroBUS socket on that system.
The mikroBUS driver tries to solve the problem by using extended version of the greybus manifest to describe the add-on board device specific information required by the device driver and uses it along with the
fixed
port specific information to probe the specific device driver.The
manifest
binary is now fetched from an I2C EEPROM over the I2C bus on the mikroBUS port(subject to change in mikroBUS v3 specification) and enumerate
drivers
for the add-on devices.There is also ongoing work to define a mikroBUS v3 standard in which the addon board includes a non-volatile storage to store the device identifier manifest binary, once the mikroBUS v3
standard
is released, the mikroBUS can be seen as a probeable bus such that the kernel can discover the device on the bus at boot time.
The driver also has a few debug SysFS interfaces for testing on add-on boards without an EEPROM, these can be used in the following manner: (example for mikroBUS port 1 on BeagleBoard.org PocketBeagle):
You should probably use debugfs, since this is a temporary thing. But I guess if it's not actually upstream (and it sounds like you might be avoiding the need for this with EEPROM anyway) maybe that doesn't matter much.
printf "%b" '\x01\x00\x00\x59\x32\x17' > /sys/bus/mikrobus/add_port
The bytes in the byte array sequence are (in order):
* i2c_adap_nr * spi_master_nr * serdev_ctlr_nr * rst_gpio_nr * pwm_gpio_nr * int_gpio_nr
- add_port sysFS entry is purely for debug and in the actual state
of the driver, port configuration will be loaded from a suitable device tree overlay fragment.
echo 0 > /sys/bus/mikrobus/del_port (to delete the attached port) echo 1 > /sys/class/mikrobus-port/mikrobus-0/rescan (to rescan the
EEPROM
contents on the I2C bus on the mikroBUS port).
cat board_manifest.mnfb > /sys/class/mikrobus-port/mikrobus-0/new_device
- debug interface to pass the manifest binary in case an EEPROM is absent
echo 0 > /sys/class/mikrobus-port/mikrobus-0/delete_device
- to unload the loaded board on the mikrobus port
These sysFS interfaces are only implemented for debug purposes and in the actual implementation of the driver these will be removed and the manifest binary will be fetched from the non volatile storage
on-board
the device.
The mikroBUS driver enable the mikroBUS to be a probeable bus such that the kernel can discover the device and automatically load the drivers. There are already several Linux platforms with mikroBUS sockets and the mikroBUS driver helps to reduce the time to develop and debug support for various mikroBUS add-on boards. Further, it opens up the possibility for support under dynamically instantiated buses such as with Greybus.
Please let know the feedback you have on this patch or the approach used.
Thanks,
Vaishnav M A
Signed-off-by: Vaishnav M A vaishnav@beagleboard.org
MAINTAINERS | 6 + drivers/misc/Kconfig | 1 + drivers/misc/Makefile | 1 + drivers/misc/mikrobus/Kconfig | 16 + drivers/misc/mikrobus/Makefile | 5 + drivers/misc/mikrobus/mikrobus_core.c | 649 ++++++++++++++++++++++ drivers/misc/mikrobus/mikrobus_core.h | 130 +++++ drivers/misc/mikrobus/mikrobus_manifest.c | 390 +++++++++++++ drivers/misc/mikrobus/mikrobus_manifest.h | 21 + include/linux/greybus/greybus_manifest.h | 53 ++ 10 files changed, 1272 insertions(+) create mode 100644 drivers/misc/mikrobus/Kconfig create mode 100644 drivers/misc/mikrobus/Makefile create mode 100644 drivers/misc/mikrobus/mikrobus_core.c create mode 100644 drivers/misc/mikrobus/mikrobus_core.h create mode 100644 drivers/misc/mikrobus/mikrobus_manifest.c create mode 100644 drivers/misc/mikrobus/mikrobus_manifest.h
diff --git a/MAINTAINERS b/MAINTAINERS index d53db30d1365..9a049746203f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11402,6 +11402,12 @@ M: Oliver Neukum oliver@neukum.org S: Maintained F: drivers/usb/image/microtek.*
+MIKROBUS ADDON BOARD DRIVER +M: Vaishnav M A vaishnav@beagleboard.org +S: Maintained +W: https://elinux.org/Mikrobus +F: drivers/misc/mikrobus/
MIPS M: Thomas Bogendoerfer tsbogend@alpha.franken.de L: linux-mips@vger.kernel.org diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index e1b1ba5e2b92..334f0c39d56b 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -472,4 +472,5 @@ source "drivers/misc/ocxl/Kconfig" source "drivers/misc/cardreader/Kconfig" source "drivers/misc/habanalabs/Kconfig" source "drivers/misc/uacce/Kconfig" +source "drivers/misc/mikrobus/Kconfig" endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index c7bd01ac6291..45486dd77da5 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -40,6 +40,7 @@ obj-$(CONFIG_VMWARE_BALLOON) += vmw_balloon.o obj-$(CONFIG_PCH_PHUB) += pch_phub.o obj-y += ti-st/ obj-y += lis3lv02d/ +obj-y += mikrobus/ obj-$(CONFIG_ALTERA_STAPL) +=altera-stapl/ obj-$(CONFIG_INTEL_MEI) += mei/ obj-$(CONFIG_VMWARE_VMCI) += vmw_vmci/ diff --git a/drivers/misc/mikrobus/Kconfig
b/drivers/misc/mikrobus/Kconfig
new file mode 100644 index 000000000000..c3b93e12daad --- /dev/null +++ b/drivers/misc/mikrobus/Kconfig @@ -0,0 +1,16 @@ +menuconfig MIKROBUS
tristate "Module for instantiating devices on mikroBUS ports"
help
This option enables the mikroBUS driver. mikroBUS is an add-on
board socket standard that offers maximum expandability with
the smallest number of pins. The mikroBUS driver helps in
instantiating devices on the mikroBUS port with identifier
data fetched from an EEPROM on the device, more details on
the mikroBUS driver support and discussion can be found in
this eLinux wiki : elinux.org/Mikrobus
This text could be cleaned up a bit. For example: The mikroBUS driver instantiates devices on a mikroBUS port described by identifying data present in device-resident EEPROM.
Using well-defined terms can help a lot. Is a physical thing that plugs into a microbus port called a "board"?
I agree language is important and should be consistent. There are mikroBUS sockets and “add-on boards”. The Mikroelektronika guys might be able to be more precise, but this ia my understanding.
Can a "board" present
more than one device to the system?
It isn’t common with existing add-on boards, but should be allowed.
Is the EEPROM associated with
the board, or a device?
Board.
My point isn't that those answers belong
here, but that having meaningful terms can help you describe things concisely.
Say Y here to enable support for this driver.
To compile this code as a module, chose M here: the module
will be called mikrobus.ko
diff --git a/drivers/misc/mikrobus/Makefile
b/drivers/misc/mikrobus/Makefile
new file mode 100644 index 000000000000..1f80ed4064d8 --- /dev/null +++ b/drivers/misc/mikrobus/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 +# mikroBUS Core
+mikrobus-y := mikrobus_core.o mikrobus_manifest.o +obj-$(CONFIG_MIKROBUS) += mikrobus.o \ No newline at end of file diff --git a/drivers/misc/mikrobus/mikrobus_core.c
b/drivers/misc/mikrobus/mikrobus_core.c
new file mode 100644 index 000000000000..78c96c637632 --- /dev/null +++ b/drivers/misc/mikrobus/mikrobus_core.c @@ -0,0 +1,649 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- mikroBUS driver for instantiating add-on
- board devices with an identifier EEPROM
- Copyright 2020 Vaishnav M A, BeagleBoard.org Foundation.
- */
+#define pr_fmt(fmt) "mikrobus: " fmt
+#include <linux/err.h> +#include <linux/errno.h> +#include <linux/idr.h> +#include <linux/init.h> +#include <linux/jump_label.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/gpio/consumer.h> +#include <linux/mutex.h> +#include <linux/device.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/gpio/machine.h> +#include <linux/nvmem-consumer.h> +#include <linux/nvmem-provider.h> +#include <linux/interrupt.h> +#include <linux/spi/spi.h> +#include <linux/serdev.h> +#include <linux/property.h> +#include <linux/slab.h>
+#include "mikrobus_core.h" +#include "mikrobus_manifest.h"
+#define ATMEL_24C32_I2C_ADDR 0x57
I'm just getting started looking through this, so maybe I'll find out soon. But why is this ATMEL I2C address needed in the core code?
+static DEFINE_IDR(mikrobus_port_idr); +static struct class_compat *mikrobus_port_compat_class; +static bool is_registered;
+static ssize_t add_port_store(struct bus_type *bt, const char *buf,
size_t count)
+{
struct mikrobus_port_config *cfg;
if (count < sizeof(*cfg)) {
Perhaps this should be: if (count != sizeof(*cfg)) {
pr_err("add_port: incorrect config data received: %s\n",
buf);
I don't think you need to include "add_port: ".
This is binary data you are writing to this sysfs file, correct? Don't try to interpret it as a string. You could avoid the problem with something more like: pr_err("bad port config size (%zu, should be %zu)", count, sizeof(*cfg));
return -EINVAL;
}
mikrobus_register_port_config((void *)buf);
mikrobus_register_port_config() returns an error value, and that should be returned from this function if it's non-zero.
Don't cast the buffer to a void pointer; cast it to the actual type represents (struct mikrobus_port_config *).
return count;
+} +BUS_ATTR_WO(add_port);
+static ssize_t del_port_store(struct bus_type *bt, const char *buf,
size_t count)
+{
int id;
char end;
int res;
res = sscanf(buf, "%d%c", &id, &end);
The id value you pass to idr_find() is an unsigned long. You might as well define "id" with that type and scan that type here. Make sure it's in the valid range for your identifier as a separate step. (There might be a good reason you use a signed int as an identifier, but I would recommend unsigned, with a well-defined number of bits, such as u32).
Is "end" intended to just consume an optional trailing newline?
if (res < 1) {
pr_err("delete_port: cannot parse mikrobus port ID\n");
I don't think "delete_port: " here is necessary. (Take this comment to apply in all similar cases; I won't mention it again.)
return -EINVAL;
}
if (!idr_find(&mikrobus_port_idr, id)) {
pr_err("attempting to delete unregistered port [%d]\n",
id);
Maybe just "mikrobus port %lu not registered".
return -EINVAL;
}
mikrobus_del_port(idr_find(&mikrobus_port_idr, id));
return count;
+} +BUS_ATTR_WO(del_port);
+static struct attribute *mikrobus_attrs[] = {
&bus_attr_add_port.attr,
&bus_attr_del_port.attr,
NULL
+}; +ATTRIBUTE_GROUPS(mikrobus);
+struct bus_type mikrobus_bus_type = {
.name = "mikrobus",
.bus_groups = mikrobus_groups,
+}; +EXPORT_SYMBOL_GPL(mikrobus_bus_type);
+static int mikrobus_manifest_header_validate(struct mikrobus_port *port) +{
char header[12];
struct addon_board_info *board;
int manifest_size;
int retval;
char *buf;
nvmem_device_read(port->eeprom, 0, 12, header);
This function returns a value, and if it's less than zero you should return it as the result of mikrobus_manifest_header_validate() (possibly after reporting an error).
manifest_size = mikrobus_manifest_header_validate(header, 12);
if (manifest_size > 0) {
buf = kzalloc(manifest_size, GFP_KERNEL);
Check whether kzalloc() returns NULL, and if so, return -ENOMEM.
nvmem_device_read(port->eeprom, 0, manifest_size, buf);
Check the return value here, and if negative, free your buffer and return the error. (I won't make this comment any more if I see other instances of ignoring the nvmem_device_read() return value.)
board = kzalloc(sizeof(*board), GFP_KERNEL);
if (!board)
return -ENOMEM;
You need to free the buffer you allocated before you return here. It is helpful to use the "goto <error path>" pattern. I.e.:
if (!board) { retval = -ENOMEM; goto err_free_buf; }
...
err_free_buf: kfree(buf);
return retval;
}
INIT_LIST_HEAD(&board->manifest_descs);
INIT_LIST_HEAD(&board->devices);
retval = mikrobus_manifest_parse(board, (void *)buf,
manifest_size);
No need to cast buf to (void *).
I have more comments on mikrobus_manifest_parse() below. But it might be useful to have it return an integer (0 or error code) rather than Boolean. Assuming you did that...
if (!retval) {
pr_err("failed to parse manifest, size: %d",
manifest_size);
if (retval) goto err_free_board;
...
err_free_board: free_board(board); err_free_buf: free_buf(buf);
return retval;
}
return -EINVAL;
}
retval = mikrobus_register_board(port, board);
if (retval) {
pr_err("failed to register board: %s",
board->name);
goto err_free_board;
return -EINVAL;
}
kfree(buf);
} else {
pr_err("inavlide manifest port %d", port->id);
s/inavlide/invalid/
return -EINVAL;
}
return 0;
+}
+static ssize_t name_show(struct device *dev, struct device_attribute
*attr,
char *buf)
+{
return sprintf(buf, "%s\n", to_mikrobus_port(dev)->name);
+} +static DEVICE_ATTR_RO(name);
+static ssize_t new_device_store(struct device *dev, struct
device_attribute *attr,
const char *buf, size_t count)
+{
struct mikrobus_port *port = to_mikrobus_port(dev);
struct addon_board_info *board;
int retval;
if (port->board == NULL) {
This is just a style suggestion, but I would prefer this, because it reduces the indentation of the normal path:
if (port->board) { pr_err("mikrobus port %d already has a board registered\n", port->id); return -EBUSY; } port->board = kzalloc(...); if (!port->board) return -ENOMEM;
Also note the return values I suggest here.
board = kzalloc(sizeof(*board), GFP_KERNEL);
if (!board)
return -EINVAL;
INIT_LIST_HEAD(&board->manifest_descs);
INIT_LIST_HEAD(&board->devices);
} else {
pr_err("port %d already has board registered", port->id);
return -EINVAL;
}
retval = mikrobus_manifest_parse(board, (void *)buf, count);
if (!retval) {
pr_err("failed to parse manifest");
return -EINVAL;
}
retval = mikrobus_register_board(port, board);
if (retval) {
pr_err("failed to register board: %s", board->name);
return -EINVAL;
}
return count;
+} +static DEVICE_ATTR_WO(new_device);
+static ssize_t rescan_store(struct device *dev, struct device_attribute
*attr,
const char *buf,
size_t count)
+{
struct mikrobus_port *port = to_mikrobus_port(dev);
int id;
char end;
int res;
int retval;
res = sscanf(buf, "%d%c", &id, &end);
if (res < 1) {
pr_err("rescan: Can't parse trigger\n");
return -EINVAL;
}
if (port->board != NULL) {
pr_err("port %d already has board registered", port->id);
return -EINVAL;
}
retval = mikrobus_port_scan_eeprom(port);
if (retval) {
pr_err("port %d board register from manifest failed",
port->id);
return -EINVAL;
}
return count;
+} +static DEVICE_ATTR_WO(rescan);
+static ssize_t delete_device_store(struct device *dev, struct
device_attribute *attr,
const char *buf,
size_t count)
+{
int id;
char end;
int res;
struct mikrobus_port *port = to_mikrobus_port(dev);
res = sscanf(buf, "%d%c", &id, &end);
if (res < 1) {
pr_err("delete_board: Can't parse board ID\n");
return -EINVAL;
}
if (port->board == NULL) {
Normally in kernel code this form is used:
if (!port->board) {
pr_err("delete_board: port does not have any boards
registered\n");
return -EINVAL;
}
mikrobus_unregister_board(port, port->board);
return count;
+} +static DEVICE_ATTR_IGNORE_LOCKDEP(delete_device, 0200, NULL,
delete_device_store);
+static struct attribute *mikrobus_port_attrs[] = {
&dev_attr_new_device.attr, &dev_attr_rescan.attr,
&dev_attr_delete_device.attr, &dev_attr_name.attr, NULL};
+ATTRIBUTE_GROUPS(mikrobus_port);
+static void mikrobus_dev_release(struct device *dev) +{
struct mikrobus_port *port = to_mikrobus_port(dev);
idr_remove(&mikrobus_port_idr, port->id);
kfree(port);
+}
+struct device_type mikrobus_port_type = {
.groups = mikrobus_port_groups,
.release = mikrobus_dev_release,
+}; +EXPORT_SYMBOL_GPL(mikrobus_port_type);
+static int mikrobus_get_irq(struct mikrobus_port *port, int irqno,
int irq_type)
+{
int irq;
switch (irqno) {
case MIKROBUS_GPIO_INT:
irq = gpiod_to_irq(port->int_gpio);
break;
Please fix your indentation here. (And everywhere; I give an example of the proper way to do it below.)
case MIKROBUS_GPIO_RST:
irq = gpiod_to_irq(port->rst_gpio);
break;
case MIKROBUS_GPIO_PWM:
irq = gpiod_to_irq(port->pwm_gpio);
break;
default:
return -EINVAL;
}
if (irq < 0) {
pr_err("Could not get irq for irq type: %d", irqno);
return -EINVAL;
}
irq_set_irq_type(irq, irq_type);
It shouldn't return an error, but please check the return value here.
return irq;
+}
+static int mikrobus_setup_gpio(struct gpio_desc *gpio, int gpio_state) +{
int retval;
if (gpio_state == MIKROBUS_GPIO_UNUSED)
return 0;
switch (gpio_state) {
case MIKROBUS_GPIO_INPUT:
retval = gpiod_direction_input(gpio);
break;
case MIKROBUS_GPIO_OUTPUT_HIGH:
retval = gpiod_direction_output(gpio, 1);
gpiod_set_value_cansleep(gpio, 1);
break;
case MIKROBUS_GPIO_OUTPUT_LOW:
retval = gpiod_direction_output(gpio, 0);
gpiod_set_value_cansleep(gpio, 0);
break;
default:
return -EINVAL;
}
return retval;
+}
+static void mikrobus_spi_device_delete(struct spi_master *master,
unsigned int cs)
+{
struct device *dev;
char str[32];
snprintf(str, sizeof(str), "%s.%u", dev_name(&master->dev), cs);
dev = bus_find_device_by_name(&spi_bus_type, NULL, str);
if (dev != NULL) {
spi_unregister_device(to_spi_device(dev));
put_device(dev);
}
+}
+static char *mikrobus_get_gpio_chip_name(struct mikrobus_port *port,
int gpio)
+{
char *name;
struct gpio_chip *gpiochip;
switch (gpio) {
case MIKROBUS_GPIO_INT:
gpiochip = gpiod_to_chip(port->int_gpio);
name = kmemdup(gpiochip->label, 40, GFP_KERNEL);
Why 40? Please use a symbolic constant so you can change it easily, and to give you a place to explain why 40 is the limit used.
break;
case MIKROBUS_GPIO_RST:
gpiochip = gpiod_to_chip(port->rst_gpio);
name = kmemdup(gpiochip->label, 40, GFP_KERNEL);
break;
case MIKROBUS_GPIO_PWM:
gpiochip = gpiod_to_chip(port->pwm_gpio);
name = kmemdup(gpiochip->label, 40, GFP_KERNEL);
break;
}
return name;
+}
+static int mikrobus_get_gpio_hwnum(struct mikrobus_port *port, int gpio) +{
int hwnum;
struct gpio_chip *gpiochip;
switch (gpio) {
case MIKROBUS_GPIO_INT:
gpiochip = gpiod_to_chip(port->int_gpio);
hwnum = desc_to_gpio(port->int_gpio) - gpiochip->base;
break;
case MIKROBUS_GPIO_RST:
gpiochip = gpiod_to_chip(port->rst_gpio);
hwnum = desc_to_gpio(port->rst_gpio) - gpiochip->base;
break;
case MIKROBUS_GPIO_PWM:
gpiochip = gpiod_to_chip(port->pwm_gpio);
hwnum = desc_to_gpio(port->pwm_gpio) - gpiochip->base;
break;
}
return hwnum;
+}
+static void release_mikrobus_device(struct board_device_info *dev)
We tend to follow a convention throughout the Greybus code that has the object name as the prefix for things you do to the object. I don't know how consistent that is, but my suggestion would be that these functions would be named something more like: mikrobus_gpio_chip_name_get() mikrobus_gpio_hwnum_get() mikrobus_board_release_device_all() mikrobus_device_register() mikrobus_device_unregister() mikrobus_board_register() mikrobus_board_unregister() and so on.
+{
list_del(&dev->links);
kfree(dev);
+}
(I skipped reviewing a lot here...) . . .
diff --git a/drivers/misc/mikrobus/mikrobus_core.h
b/drivers/misc/mikrobus/mikrobus_core.h
new file mode 100644 index 000000000000..9684d315f564 --- /dev/null +++ b/drivers/misc/mikrobus/mikrobus_core
. . .
diff --git a/drivers/misc/mikrobus/mikrobus_manifest.c
b/drivers/misc/mikrobus/mikrobus_manifest.c
new file mode 100644 index 000000000000..60ebca560f0d --- /dev/null +++ b/drivers/misc/mikrobus/mikrobus_manifest.c @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- mikroBUS manifest parsing, an
- extension to Greybus Manifest Parsing
- under drivers/greybus/manifest.c
- Copyright 2014-2015 Google Inc.
- Copyright 2014-2015 Linaro Ltd.
- */
+#define pr_fmt(fmt) "mikrobus_manifest: " fmt
+#include <linux/bits.h> +#include <linux/types.h> +#include <linux/property.h> +#include <linux/greybus/greybus_manifest.h>
+#include "mikrobus_manifest.h"
+struct manifest_desc {
struct list_head links;
size_t size;
void *data;
enum greybus_descriptor_type type;
+};
+static void release_manifest_descriptor(struct manifest_desc
*descriptor)
+{
list_del(&descriptor->links);
kfree(descriptor);
+}
+static void release_manifest_descriptors(struct addon_board_info *info) +{
struct manifest_desc *descriptor;
struct manifest_desc *next;
list_for_each_entry_safe(descriptor, next, &info->manifest_descs,
links)
release_manifest_descriptor(descriptor);
+}
+static int identify_descriptor(struct addon_board_info *info, struct
greybus_descriptor *desc,
size_t size)
+{
I know it's technically acceptable now, but please limit your lines to 80 characters in the Greybus code if possible.
struct greybus_descriptor_header *desc_header = &desc->header;
struct manifest_desc *descriptor;
size_t desc_size;
size_t expected_size;
if (size < sizeof(*desc_header))
return -EINVAL;
desc_size = le16_to_cpu(desc_header->size);
if (desc_size > size)
return -EINVAL;
Probably check if (desc_size != size) here.
expected_size = sizeof(*desc_header);
switch (desc_header->type) {
case GREYBUS_TYPE_STRING:
expected_size += sizeof(struct greybus_descriptor_string);
expected_size += desc->string.length;
expected_size = ALIGN(expected_size, 4);
break;
Your indentation is wrong here. Please use:
switch (desc_header->type) { case GREYBUS_TYPE_STRING: expected_size += ...; ... break; case GREYBUS_TYPE_PROPERTY: ...
case GREYBUS_TYPE_PROPERTY:
expected_size += sizeof(struct greybus_descriptor_property);
expected_size += desc->property.length;
expected_size = ALIGN(expected_size, 4);
break;
case GREYBUS_TYPE_DEVICE:
expected_size += sizeof(struct greybus_descriptor_device);
break;
case GREYBUS_TYPE_MIKROBUS:
expected_size += sizeof(struct greybus_descriptor_mikrobus);
break;
case GREYBUS_TYPE_INTERFACE:
expected_size += sizeof(struct greybus_descriptor_interface);
break;
case GREYBUS_TYPE_CPORT:
expected_size += sizeof(struct greybus_descriptor_cport);
break;
case GREYBUS_TYPE_BUNDLE:
expected_size += sizeof(struct greybus_descriptor_bundle);
break;
case GREYBUS_TYPE_INVALID:
default:
return -EINVAL;
}
descriptor = kzalloc(sizeof(*descriptor), GFP_KERNEL);
if (!descriptor)
return -ENOMEM;
descriptor->size = desc_size;
descriptor->data = (char *)desc + sizeof(*desc_header);
descriptor->type = desc_header->type;
list_add_tail(&descriptor->links, &info->manifest_descs);
return desc_size;
+}
. . .
+static int mikrobus_manifest_attach_device(struct addon_board_info
*info,
struct
greybus_descriptor_device *dev_desc)
+{
struct board_device_info *dev;
I would suggest something other than "dev" as the name of a board_device. The use of "dev" for (struct device *) is pretty prevalent in the kernel, and so using it here can be more confusing than it has to be.
struct gpiod_lookup_table *lookup;
struct greybus_descriptor_property *desc_property;
struct manifest_desc *descriptor;
int i;
u8 *prop_link;
u8 *gpio_desc_link;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
dev->id = dev_desc->id;
dev->drv_name = mikrobus_string_get(info,
dev_desc->driver_stringid);
This can return NULL. You need to check for that, and free the board device you have already allocated.
dev->protocol = dev_desc->protocol;
dev->reg = dev_desc->reg;
dev->irq = dev_desc->irq;
dev->irq_type = dev_desc->irq_type;
dev->max_speed_hz = le32_to_cpu(dev_desc->max_speed_hz);
dev->mode = dev_desc->mode;
dev->cs_gpio = dev_desc->cs_gpio;
dev->num_gpio_resources = dev_desc->num_gpio_resources;
dev->num_properties = dev_desc->num_properties;
pr_info("device %d , number of properties=%d , number of gpio
resources=%d\n",
dev->id, dev->num_properties, dev->num_gpio_resources);
if (dev->num_properties > 0) {
prop_link = mikrobus_property_link_get(info,
dev_desc->prop_link,
MIKROBUS_PROPERTY_TYPE_LINK);
dev->properties = mikrobus_property_entry_get(info,
prop_link, dev->num_properties);
}
if (dev->num_gpio_resources > 0) {
lookup = kzalloc(struct_size(lookup, table,
dev->num_gpio_resources),
GFP_KERNEL);
if (!lookup)
return -ENOMEM;
You can't return without freeing your previously-allocated resources.
gpio_desc_link = mikrobus_property_link_get(info,
dev_desc->gpio_link,
MIKROBUS_PROPERTY_TYPE_GPIO);
for (i = 0; i < dev->num_gpio_resources; i++) {
list_for_each_entry(descriptor, &info->manifest_descs,
links) {
if (descriptor->type != GREYBUS_TYPE_PROPERTY)
continue;
desc_property = descriptor->data;
if (desc_property->id == gpio_desc_link[i]) {
lookup->table[i].chip_hwnum =
*desc_property->value;
lookup->table[i].con_id =
mikrobus_string_get(info,
desc_property->propname_stringid);
break;
}
}
}
dev->gpio_lookup = lookup;
}
list_add_tail(&dev->links, &info->devices);
return 0;
+}
+static int mikrobus_manifest_parse_devices(struct addon_board_info
*info)
+{
struct greybus_descriptor_device *desc_device;
struct manifest_desc *desc, *next;
int devcount = 0;
if (WARN_ON(!list_empty(&info->devices)))
return false;
The manifest comes from outside the kernel I might be misunderstanding something, but you should not be using WARN_ON() if its content doesn't match what you expect.
list_for_each_entry_safe(desc, next, &info->manifest_descs, links)
{
if (desc->type != GREYBUS_TYPE_DEVICE)
continue;
desc_device = desc->data;
mikrobus_manifest_attach_device(info, desc_device);
You are ignoring the return value of mikrobus_manifest_attach_device().
devcount++;
}
return devcount;
+}
+bool mikrobus_manifest_parse(struct addon_board_info *info, void *data,
size_t size)
You use "board" as the name of a "board_info" variable elsewhere. That is much more helpful than "info". Please use a consistent naming convention for your variables of given types if possible. It makes it easier to follow the code.
+{
struct greybus_manifest *manifest;
struct greybus_manifest_header *header;
struct greybus_descriptor *desc;
u16 manifest_size;
int dev_count;
int desc_size;
Check the size before you bother checking anything else.
if (WARN_ON(!list_empty(&info->manifest_descs)))
return false;
if (size < sizeof(*header))
return false;
manifest = data;
header = &manifest->header;
manifest_size = le16_to_cpu(header->size);
if (manifest_size != size)
return false;
It would be helpful to report what the problem with the manifest is (here and in all cases). Otherwise it's a little mysterious why adding a board fails.
if (header->version_major > MIKROBUS_VERSION_MAJOR)
return false;
Version checks!!! This is good! But the topic is of great interest to Greybus people! Not sure we ever completely resolved that. That's my only comment on this for now.
desc = manifest->descriptors;
size -= sizeof(*header);
Why aren't you using mikrobus_manifest_header_validate() here?
while (size) {
desc_size = identify_descriptor(info, desc, size);
What you're really doing with identify_descriptor() is adding a valid descriptor to a board's list of descriptors. I think the name of that function shoudl reflect that. If it isn't identified, that's an error--but that's not the purpose of that function. So maybe: desc_ = board_descriptor_add(board, desc, size);
if (desc_size < 0) {
pr_err("invalid manifest descriptor");
return -EINVAL;
Your indentation of the return statement here is wrong.
}
desc = (struct greybus_descriptor *)((char *)desc +
desc_size);
I don't know if this is better, but this could be: desc = (void *)desc + desc_size;
size -= desc_size;
}
mikrobus_state_get(info);
dev_count = mikrobus_manifest_parse_devices(info);
pr_info(" %s manifest parsed with %d device(s)\n", info->name,
info->num_devices);
release_manifest_descriptors(info);
return true;
+}
+size_t mikrobus_manifest_header_validate(void *data, size_t size) +{
struct greybus_manifest_header *header;
u16 manifest_size;
if (size < sizeof(*header))
return 0;
header = data;
manifest_size = le16_to_cpu(header->size);
if (header->version_major > MIKROBUS_VERSION_MAJOR)
return 0;
return manifest_size;
+} diff --git a/drivers/misc/mikrobus/mikrobus_manifest.h
b/drivers/misc/mikrobus/mikrobus_manifest.h
new file mode 100644 index 000000000000..a195d1c26493 --- /dev/null +++ b/drivers/misc/mikrobus/mikrobus_manifest.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/*
- mikroBUS manifest definition
- extension to Greybus Manifest Definition
- Copyright 2014-2015 Google Inc.
- Copyright 2014-2015 Linaro Ltd.
- Released under the GPLv2 and BSD licenses.
- */
+#ifndef __MIKROBUS_MANIFEST_H +#define __MIKROBUS_MANIFEST_H
+#include "mikrobus_core.h"
+bool mikrobus_manifest_parse(struct addon_board_info *info, void *data,
size_t size);
+size_t mikrobus_manifest_header_validate(void *data, size_t size);
+#endif /* __MIKROBUS_MANIFEST_H */ diff --git a/include/linux/greybus/greybus_manifest.h
b/include/linux/greybus/greybus_manifest.h
index 6e62fe478712..79c8cab9ef96 100644 --- a/include/linux/greybus/greybus_manifest.h +++ b/include/linux/greybus/greybus_manifest.h @@ -23,6 +23,9 @@ enum greybus_descriptor_type { GREYBUS_TYPE_STRING = 0x02, GREYBUS_TYPE_BUNDLE = 0x03, GREYBUS_TYPE_CPORT = 0x04,
GREYBUS_TYPE_MIKROBUS = 0x05,
GREYBUS_TYPE_PROPERTY = 0x06,
GREYBUS_TYPE_DEVICE = 0x07,
Please align your new values with the rest, for consistency.
};
enum greybus_protocol { @@ -151,6 +154,53 @@ struct greybus_descriptor_cport { __u8 protocol_id; /* enum greybus_protocol */ } __packed;
+/*
- A mikrobus descriptor is used to describe the details
- about the add-on board connected to the mikrobus port.
- It describes the number of indivdual devices on the
- add-on board and the default states of the GPIOs
- */
+struct greybus_descriptor_mikrobus {
__u8 num_devices;
__u8 rst_gpio_state;
__u8 pwm_gpio_state;
__u8 int_gpio_state;
+} __packed;
+/*
- A property descriptor is used to pass named properties
- to device drivers through the unified device properties
- interface under linux/property.h
- */
+struct greybus_descriptor_property {
__u8 length;
__u8 id;
__u8 propname_stringid;
__u8 type;
__u8 value[0];
+} __packed;
+/*
- A device descriptor is used to describe the
- details required by a add-on board device
- driver.
- */
+struct greybus_descriptor_device {
__u8 id;
__u8 driver_stringid;
__u8 num_properties;
__u8 protocol;
__le32 max_speed_hz;
__u8 reg;
__u8 mode;
__u8 num_gpio_resources;
__u8 cs_gpio;
__u8 irq;
__u8 irq_type;
__u8 prop_link;
__u8 gpio_link;
+} __packed;
struct greybus_descriptor_header { __le16 size; __u8 type; /* enum greybus_descriptor_type */ @@ -164,6 +214,9 @@ struct greybus_descriptor { struct greybus_descriptor_interface interface; struct greybus_descriptor_bundle bundle; struct greybus_descriptor_cport cport;
struct greybus_descriptor_mikrobus mikrobus;
struct greybus_descriptor_property property;
struct greybus_descriptor_device device;
We're going to need to talk about these things... But I can't comment much more without learning more about the broader architecture.
-Alex
};
} __packed;
greybus-dev mailing list greybus-dev@lists.linaro.org https://lists.linaro.org/mailman/listinfo/greybus-dev