summaryrefslogblamecommitdiffstats
path: root/drivers/staging/greybus/manifest.c
blob: 8b61b3486a3df90365d6045e13ec2ff832802555 (plain) (tree)
1
2
3
4
5



                                  
                             
























                                                                    





                                                                         
                                                                   



                                         
                                                                                




                                                                    
                                                               






                                                                    

                                                                            
























                                                                           
                                 
                                                     







                                                                          

                                    






                                                                          


                                                               










                                                                              
                                                             
                                             
                                                                 














                                                                    
                                                                   









                                                             
                                                                       



                                                            
                                               




















                                                                               
  
                                                                    

                                                                  
   

                                                              





                                                            
                               
                             
                                   

                                             
                                                                               
                                                                     
                                                              
                                                                       


                                                     





                                                               
                                                      
                                                       
                                                                         










                                                        

                                                                

                
                                                               





                                                                    
                                         

                                   
                                               
                                                                               
                                                                         






                                             
                                                            
                                                  
                                                                    
                            
                                                   
 
                                                                      
                                                            

                                                                  

                        
                                                      





                                                        

                                                                       
 
                                                                          

                                                     
                                                                                
                                        

                             

                                                                            
                                           
                                            

         


                                                              



                                                                        
                                                                   
                                               
                                                                  
                             

         

                    

                                    
                       

                                   

                     














                                                                  
                                                                 



                                                                    
                                                                   

                      
                                                           
   
                                                                          




                                               
                                                 

                          
                    
 
                                                           
                                                        

                             


                                                            
                             








                                                             
                             






                                                                            
                             







                                                         
                                                                  


                                                                           
                                       
                                 


                                                                               
         
 
                                                      
                                                                       

                                                            
                                                         





                                                                             


                                                                            
                                                             




                                                                 
                                                         
                                                                   
    
                                           
 
                      
 
/*
 * Greybus module manifest parsing
 *
 * Copyright 2014 Google Inc.
 * Copyright 2014 Linaro Ltd.
 *
 * Released under the GPLv2 only.
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/err.h>

#include "greybus.h"

/*
 * We scan the manifest once to identify where all the descriptors
 * are.  The result is a list of these manifest_desc structures.  We
 * then pick through them for what we're looking for (starting with
 * the module descriptor).  As each is processed we remove it from
 * the list.  When we're done the list should (probably) be empty.
 */
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 gb_interface *intf)
{
	struct manifest_desc *descriptor;
	struct manifest_desc *next;

	list_for_each_entry_safe(descriptor, next, &intf->manifest_descs, links)
		release_manifest_descriptor(descriptor);
}

/*
 * Validate the given descriptor.  Its reported size must fit within
 * the number of bytes remaining, and it must have a recognized
 * type.  Check that the reported size is at least as big as what
 * we expect to see.  (It could be bigger, perhaps for a new version
 * of the format.)
 *
 * Returns the number of bytes consumed by the descriptor, or a
 * negative errno.
 */
static int identify_descriptor(struct gb_interface *intf,
			       struct greybus_descriptor *desc, size_t size)
{
	struct greybus_descriptor_header *desc_header = &desc->header;
	struct manifest_desc *descriptor;
	int desc_size;
	size_t expected_size;

	if (size < sizeof(*desc_header)) {
		pr_err("manifest too small\n");
		return -EINVAL;		/* Must at least have header */
	}

	desc_size = (int)le16_to_cpu(desc_header->size);
	if ((size_t)desc_size > size) {
		pr_err("descriptor too big\n");
		return -EINVAL;
	}

	switch (desc_header->type) {
	case GREYBUS_TYPE_MODULE:
		if (desc_size < sizeof(struct greybus_descriptor_module)) {
			pr_err("module descriptor too small (%u)\n",
				desc_size);
			return -EINVAL;
		}
		break;
	case GREYBUS_TYPE_STRING:
		expected_size = sizeof(*desc_header);
		expected_size += sizeof(struct greybus_descriptor_string);
		expected_size += (size_t)desc->string.length;
		if (desc_size < expected_size) {
			pr_err("string descriptor too small (%u)\n",
				desc_size);
			return -EINVAL;
		}
		break;
	case GREYBUS_TYPE_INTERFACE:
		break;
	case GREYBUS_TYPE_CPORT:
		if (desc_size < sizeof(struct greybus_descriptor_cport)) {
			pr_err("cport descriptor too small (%u)\n",
				desc_size);
			return -EINVAL;
		}
		break;
	case GREYBUS_TYPE_CLASS:
		pr_warn("class descriptor found (ignoring)\n");
		break;
	case GREYBUS_TYPE_INVALID:
	default:
		pr_err("invalid descriptor type (%hhu)\n", desc_header->type);
		return -EINVAL;
	}

	descriptor = kzalloc(sizeof(*descriptor), GFP_KERNEL);
	if (!descriptor)
		return -ENOMEM;

	descriptor->size = desc_size;
	descriptor->data = (u8 *)desc + sizeof(*desc_header);
	descriptor->type = desc_header->type;
	list_add_tail(&descriptor->links, &intf->manifest_descs);

	return desc_size;
}

/*
 * Find the string descriptor having the given id, validate it, and
 * allocate a duplicate copy of it.  The duplicate has an extra byte
 * which guarantees the returned string is NUL-terminated.
 *
 * String index 0 is valid (it represents "no string"), and for
 * that a null pointer is returned.
 *
 * Otherwise returns a pointer to a newly-allocated copy of the
 * descriptor string, or an error-coded pointer on failure.
 */
static char *gb_string_get(struct gb_interface *intf, u8 string_id)
{
	struct greybus_descriptor_string *desc_string;
	struct manifest_desc *descriptor;
	bool found = false;
	char *string;

	/* A zero string id means no string (but no error) */
	if (!string_id)
		return NULL;

	list_for_each_entry(descriptor, &intf->manifest_descs, links) {

		if (descriptor->type != GREYBUS_TYPE_STRING)
			continue;

		desc_string = descriptor->data;
		if (desc_string->id == string_id) {
			found = true;
			break;
		}
	}
	if (!found)
		return ERR_PTR(-ENOENT);

	/* Allocate an extra byte so we can guarantee it's NUL-terminated */
	string = kmemdup(&desc_string->string, (size_t)desc_string->length + 1,
				GFP_KERNEL);
	if (!string)
		return ERR_PTR(-ENOMEM);
	string[desc_string->length] = '\0';

	/* Ok we've used this string, so we're done with it */
	release_manifest_descriptor(descriptor);

	return string;
}

/*
 * Find cport descriptors in the manifest and set up data structures
 * for the functions that use them.  Returns the number of bundles
 * set up for the given interface, or 0 if there is an error.
 */
static u32 gb_manifest_parse_cports(struct gb_interface *intf,
				    struct gb_bundle *bundle)
{
	u32 count = 0;

	while (true) {
		struct manifest_desc *descriptor;
		struct greybus_descriptor_cport *desc_cport;
		u8 protocol_id;
		u16 cport_id;
		bool found = false;

		/* Find a cport descriptor */
		list_for_each_entry(descriptor, &intf->manifest_descs, links) {
			if (descriptor->type == GREYBUS_TYPE_CPORT) {
				desc_cport = descriptor->data;
				if (desc_cport->bundle == bundle->id) {
					found = true;
					break;
				}
			}
		}
		if (!found)
			break;

		/* Found one.  Set up its function structure */
		protocol_id = desc_cport->protocol_id;
		cport_id = le16_to_cpu(desc_cport->id);
		if (!gb_connection_create(bundle, cport_id, protocol_id))
			return 0;	/* Error */

		count++;
		/* Release the cport descriptor */
		release_manifest_descriptor(descriptor);
	}

	return count;
}

/*
 * Find bundle descriptors in the manifest and set up their data
 * structures.  Returns the number of bundles set up for the
 * given module.
 */
static u32 gb_manifest_parse_bundles(struct gb_interface *intf)
{
	u32 count = 0;

	while (true) {
		struct manifest_desc *descriptor;
		struct greybus_descriptor_interface *desc_interface;
		struct gb_bundle *bundle;
		bool found = false;

		/* Find an bundle descriptor */
		list_for_each_entry(descriptor, &intf->manifest_descs, links) {
			if (descriptor->type == GREYBUS_TYPE_INTERFACE) {
				found = true;
				break;
			}
		}
		if (!found)
			break;

		/* Found one.  Set up its bundle structure*/
		desc_interface = descriptor->data;
		bundle = gb_bundle_create(intf, desc_interface->id);
		if (!bundle)
			return 0;	/* Error */

		/* Now go set up this bundle's functions and cports */
		if (!gb_manifest_parse_cports(intf, bundle))
			return 0;	/* Error parsing cports */

		count++;

		/* Done with this bundle descriptor */
		release_manifest_descriptor(descriptor);
	}

	return count;
}

static bool gb_manifest_parse_module(struct gb_interface *intf,
				     struct manifest_desc *module_desc)
{
	struct greybus_descriptor_module *desc_module = module_desc->data;

	/* Handle the strings first--they can fail */
	intf->vendor_string = gb_string_get(intf, desc_module->vendor_stringid);
	if (IS_ERR(intf->vendor_string))
		return false;

	intf->product_string = gb_string_get(intf,
					     desc_module->product_stringid);
	if (IS_ERR(intf->product_string)) {
		goto out_free_vendor_string;
	}

	intf->vendor = le16_to_cpu(desc_module->vendor);
	intf->product = le16_to_cpu(desc_module->product);
	intf->unique_id = le64_to_cpu(desc_module->unique_id);

	/* Release the module descriptor, now that we're done with it */
	release_manifest_descriptor(module_desc);

	/* An interface must have at least one bundle descriptor */
	if (!gb_manifest_parse_bundles(intf)) {
		pr_err("manifest bundle descriptors not valid\n");
		goto out_err;
	}

	return true;
out_err:
	kfree(intf->product_string);
	intf->product_string = NULL;
out_free_vendor_string:
	kfree(intf->vendor_string);
	intf->vendor_string = NULL;

	return false;
}

/*
 * Parse a buffer containing a module manifest.
 *
 * If we find anything wrong with the content/format of the buffer
 * we reject it.
 *
 * The first requirement is that the manifest's version is
 * one we can parse.
 *
 * We make an initial pass through the buffer and identify all of
 * the descriptors it contains, keeping track for each its type
 * and the location size of its data in the buffer.
 *
 * Next we scan the descriptors, looking for a module descriptor;
 * there must be exactly one of those.  When found, we record the
 * information it contains, and then remove that descriptor (and any
 * string descriptors it refers to) from further consideration.
 *
 * After that we look for the interface's bundles--there must be at
 * least one of those.
 *
 * Returns true if parsing was successful, false otherwise.
 */
bool gb_manifest_parse(struct gb_interface *intf, void *data, size_t size)
{
	struct greybus_manifest *manifest;
	struct greybus_manifest_header *header;
	struct greybus_descriptor *desc;
	struct manifest_desc *descriptor;
	struct manifest_desc *module_desc = NULL;
	u16 manifest_size;
	u32 found = 0;
	bool result;

	/* Manifest descriptor list should be empty here */
	if (WARN_ON(!list_empty(&intf->manifest_descs)))
		return false;

	/* we have to have at _least_ the manifest header */
	if (size <= sizeof(manifest->header)) {
		pr_err("short manifest (%zu)\n", size);
		return false;
	}

	/* Make sure the size is right */
	manifest = data;
	header = &manifest->header;
	manifest_size = le16_to_cpu(header->size);
	if (manifest_size != size) {
		pr_err("manifest size mismatch %zu != %hu\n",
			size, manifest_size);
		return false;
	}

	/* Validate major/minor number */
	if (header->version_major > GREYBUS_VERSION_MAJOR) {
		pr_err("manifest version too new (%hhu.%hhu > %hhu.%hhu)\n",
			header->version_major, header->version_minor,
			GREYBUS_VERSION_MAJOR, GREYBUS_VERSION_MINOR);
		return false;
	}

	/* OK, find all the descriptors */
	desc = (struct greybus_descriptor *)(header + 1);
	size -= sizeof(*header);
	while (size) {
		int desc_size;

		desc_size = identify_descriptor(intf, desc, size);
		if (desc_size <= 0) {
			if (!desc_size)
				pr_err("zero-sized manifest descriptor\n");
			result = false;
			goto out;
		}
		desc = (struct greybus_descriptor *)((char *)desc + desc_size);
		size -= desc_size;
	}

	/* There must be a single module descriptor */
	list_for_each_entry(descriptor, &intf->manifest_descs, links) {
		if (descriptor->type == GREYBUS_TYPE_MODULE)
			if (!found++)
				module_desc = descriptor;
	}
	if (found != 1) {
		pr_err("manifest must have 1 module descriptor (%u found)\n",
			found);
		result = false;
		goto out;
	}

	/* Parse the module manifest, starting with the module descriptor */
	result = gb_manifest_parse_module(intf, module_desc);

	/*
	 * We really should have no remaining descriptors, but we
	 * don't know what newer format manifests might leave.
	 */
	if (result && !list_empty(&intf->manifest_descs))
		pr_info("excess descriptors in module manifest\n");
out:
	release_manifest_descriptors(intf);

	return result;
}