forked from mirrors/linux
		
	devcoredump: add scatterlist support
Add scatterlist support (dev_coredumpsg) to allow drivers to avoid vmalloc() like dev_coredumpm(), while also avoiding the module reference that the latter function requires. This internally uses dev_coredumpm() with function inside the devcoredump module, requiring removing the const (which touches the driver using it.) Signed-off-by: Aviya Erenfeld <aviya.erenfeld@intel.com> Signed-off-by: Johannes Berg <johannes.berg@intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
		
							parent
							
								
									c4a74f63df
								
							
						
					
					
						commit
						522566376a
					
				
					 3 changed files with 154 additions and 19 deletions
				
			
		|  | @ -4,6 +4,7 @@ | ||||||
|  * GPL LICENSE SUMMARY |  * GPL LICENSE SUMMARY | ||||||
|  * |  * | ||||||
|  * Copyright(c) 2014 Intel Mobile Communications GmbH |  * Copyright(c) 2014 Intel Mobile Communications GmbH | ||||||
|  |  * Copyright(c) 2015 Intel Deutschland GmbH | ||||||
|  * |  * | ||||||
|  * This program is free software; you can redistribute it and/or modify |  * This program is free software; you can redistribute it and/or modify | ||||||
|  * it under the terms of version 2 of the GNU General Public License as |  * it under the terms of version 2 of the GNU General Public License as | ||||||
|  | @ -41,12 +42,12 @@ static bool devcd_disabled; | ||||||
| 
 | 
 | ||||||
| struct devcd_entry { | struct devcd_entry { | ||||||
| 	struct device devcd_dev; | 	struct device devcd_dev; | ||||||
| 	const void *data; | 	void *data; | ||||||
| 	size_t datalen; | 	size_t datalen; | ||||||
| 	struct module *owner; | 	struct module *owner; | ||||||
| 	ssize_t (*read)(char *buffer, loff_t offset, size_t count, | 	ssize_t (*read)(char *buffer, loff_t offset, size_t count, | ||||||
| 			const void *data, size_t datalen); | 			void *data, size_t datalen); | ||||||
| 	void (*free)(const void *data); | 	void (*free)(void *data); | ||||||
| 	struct delayed_work del_wk; | 	struct delayed_work del_wk; | ||||||
| 	struct device *failing_dev; | 	struct device *failing_dev; | ||||||
| }; | }; | ||||||
|  | @ -174,7 +175,7 @@ static struct class devcd_class = { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static ssize_t devcd_readv(char *buffer, loff_t offset, size_t count, | static ssize_t devcd_readv(char *buffer, loff_t offset, size_t count, | ||||||
| 			   const void *data, size_t datalen) | 			   void *data, size_t datalen) | ||||||
| { | { | ||||||
| 	if (offset > datalen) | 	if (offset > datalen) | ||||||
| 		return -EINVAL; | 		return -EINVAL; | ||||||
|  | @ -188,6 +189,11 @@ static ssize_t devcd_readv(char *buffer, loff_t offset, size_t count, | ||||||
| 	return count; | 	return count; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void devcd_freev(void *data) | ||||||
|  | { | ||||||
|  | 	vfree(data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /**
 | /**
 | ||||||
|  * dev_coredumpv - create device coredump with vmalloc data |  * dev_coredumpv - create device coredump with vmalloc data | ||||||
|  * @dev: the struct device for the crashed device |  * @dev: the struct device for the crashed device | ||||||
|  | @ -198,10 +204,10 @@ static ssize_t devcd_readv(char *buffer, loff_t offset, size_t count, | ||||||
|  * This function takes ownership of the vmalloc'ed data and will free |  * This function takes ownership of the vmalloc'ed data and will free | ||||||
|  * it when it is no longer used. See dev_coredumpm() for more information. |  * it when it is no longer used. See dev_coredumpm() for more information. | ||||||
|  */ |  */ | ||||||
| void dev_coredumpv(struct device *dev, const void *data, size_t datalen, | void dev_coredumpv(struct device *dev, void *data, size_t datalen, | ||||||
| 		   gfp_t gfp) | 		   gfp_t gfp) | ||||||
| { | { | ||||||
| 	dev_coredumpm(dev, NULL, data, datalen, gfp, devcd_readv, vfree); | 	dev_coredumpm(dev, NULL, data, datalen, gfp, devcd_readv, devcd_freev); | ||||||
| } | } | ||||||
| EXPORT_SYMBOL_GPL(dev_coredumpv); | EXPORT_SYMBOL_GPL(dev_coredumpv); | ||||||
| 
 | 
 | ||||||
|  | @ -212,6 +218,44 @@ static int devcd_match_failing(struct device *dev, const void *failing) | ||||||
| 	return devcd->failing_dev == failing; | 	return devcd->failing_dev == failing; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /**
 | ||||||
|  |  * devcd_free_sgtable - free all the memory of the given scatterlist table | ||||||
|  |  * (i.e. both pages and scatterlist instances) | ||||||
|  |  * NOTE: if two tables allocated with devcd_alloc_sgtable and then chained | ||||||
|  |  * using the sg_chain function then that function should be called only once | ||||||
|  |  * on the chained table | ||||||
|  |  * @table: pointer to sg_table to free | ||||||
|  |  */ | ||||||
|  | static void devcd_free_sgtable(void *data) | ||||||
|  | { | ||||||
|  | 	_devcd_free_sgtable(data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * devcd_read_from_table - copy data from sg_table to a given buffer | ||||||
|  |  * and return the number of bytes read | ||||||
|  |  * @buffer: the buffer to copy the data to it | ||||||
|  |  * @buf_len: the length of the buffer | ||||||
|  |  * @data: the scatterlist table to copy from | ||||||
|  |  * @offset: start copy from @offset@ bytes from the head of the data | ||||||
|  |  *	in the given scatterlist | ||||||
|  |  * @data_len: the length of the data in the sg_table | ||||||
|  |  */ | ||||||
|  | static ssize_t devcd_read_from_sgtable(char *buffer, loff_t offset, | ||||||
|  | 				       size_t buf_len, void *data, | ||||||
|  | 				       size_t data_len) | ||||||
|  | { | ||||||
|  | 	struct scatterlist *table = data; | ||||||
|  | 
 | ||||||
|  | 	if (offset > data_len) | ||||||
|  | 		return -EINVAL; | ||||||
|  | 
 | ||||||
|  | 	if (offset + buf_len > data_len) | ||||||
|  | 		buf_len = data_len - offset; | ||||||
|  | 	return sg_pcopy_to_buffer(table, sg_nents(table), buffer, buf_len, | ||||||
|  | 				  offset); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /**
 | /**
 | ||||||
|  * dev_coredumpm - create device coredump with read/free methods |  * dev_coredumpm - create device coredump with read/free methods | ||||||
|  * @dev: the struct device for the crashed device |  * @dev: the struct device for the crashed device | ||||||
|  | @ -228,10 +272,10 @@ static int devcd_match_failing(struct device *dev, const void *failing) | ||||||
|  * function will be called to free the data. |  * function will be called to free the data. | ||||||
|  */ |  */ | ||||||
| void dev_coredumpm(struct device *dev, struct module *owner, | void dev_coredumpm(struct device *dev, struct module *owner, | ||||||
| 		   const void *data, size_t datalen, gfp_t gfp, | 		   void *data, size_t datalen, gfp_t gfp, | ||||||
| 		   ssize_t (*read)(char *buffer, loff_t offset, size_t count, | 		   ssize_t (*read)(char *buffer, loff_t offset, size_t count, | ||||||
| 				   const void *data, size_t datalen), | 				   void *data, size_t datalen), | ||||||
| 		   void (*free)(const void *data)) | 		   void (*free)(void *data)) | ||||||
| { | { | ||||||
| 	static atomic_t devcd_count = ATOMIC_INIT(0); | 	static atomic_t devcd_count = ATOMIC_INIT(0); | ||||||
| 	struct devcd_entry *devcd; | 	struct devcd_entry *devcd; | ||||||
|  | @ -291,6 +335,27 @@ void dev_coredumpm(struct device *dev, struct module *owner, | ||||||
| } | } | ||||||
| EXPORT_SYMBOL_GPL(dev_coredumpm); | EXPORT_SYMBOL_GPL(dev_coredumpm); | ||||||
| 
 | 
 | ||||||
|  | /**
 | ||||||
|  |  * dev_coredumpmsg - create device coredump that uses scatterlist as data | ||||||
|  |  * parameter | ||||||
|  |  * @dev: the struct device for the crashed device | ||||||
|  |  * @table: the dump data | ||||||
|  |  * @datalen: length of the data | ||||||
|  |  * @gfp: allocation flags | ||||||
|  |  * | ||||||
|  |  * Creates a new device coredump for the given device. If a previous one hasn't | ||||||
|  |  * been read yet, the new coredump is discarded. The data lifetime is determined | ||||||
|  |  * by the device coredump framework and when it is no longer needed | ||||||
|  |  * it will free the data. | ||||||
|  |  */ | ||||||
|  | void dev_coredumpsg(struct device *dev, struct scatterlist *table, | ||||||
|  | 		    size_t datalen, gfp_t gfp) | ||||||
|  | { | ||||||
|  | 	dev_coredumpm(dev, NULL, table, datalen, gfp, devcd_read_from_sgtable, | ||||||
|  | 		      devcd_free_sgtable); | ||||||
|  | } | ||||||
|  | EXPORT_SYMBOL_GPL(dev_coredumpsg); | ||||||
|  | 
 | ||||||
| static int __init devcoredump_init(void) | static int __init devcoredump_init(void) | ||||||
| { | { | ||||||
| 	return class_register(&devcd_class); | 	return class_register(&devcd_class); | ||||||
|  |  | ||||||
|  | @ -71,7 +71,7 @@ | ||||||
| #include "iwl-csr.h" | #include "iwl-csr.h" | ||||||
| 
 | 
 | ||||||
| static ssize_t iwl_mvm_read_coredump(char *buffer, loff_t offset, size_t count, | static ssize_t iwl_mvm_read_coredump(char *buffer, loff_t offset, size_t count, | ||||||
| 				     const void *data, size_t datalen) | 				     void *data, size_t datalen) | ||||||
| { | { | ||||||
| 	const struct iwl_mvm_dump_ptrs *dump_ptrs = data; | 	const struct iwl_mvm_dump_ptrs *dump_ptrs = data; | ||||||
| 	ssize_t bytes_read; | 	ssize_t bytes_read; | ||||||
|  | @ -104,7 +104,7 @@ static ssize_t iwl_mvm_read_coredump(char *buffer, loff_t offset, size_t count, | ||||||
| 	return bytes_read + bytes_read_trans; | 	return bytes_read + bytes_read_trans; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void iwl_mvm_free_coredump(const void *data) | static void iwl_mvm_free_coredump(void *data) | ||||||
| { | { | ||||||
| 	const struct iwl_mvm_dump_ptrs *fw_error_dump = data; | 	const struct iwl_mvm_dump_ptrs *fw_error_dump = data; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,3 +1,22 @@ | ||||||
|  | /*
 | ||||||
|  |  * This file is provided under the GPLv2 license. | ||||||
|  |  * | ||||||
|  |  * GPL LICENSE SUMMARY | ||||||
|  |  * | ||||||
|  |  * Copyright(c) 2015 Intel Deutschland GmbH | ||||||
|  |  * | ||||||
|  |  * This program is free software; you can redistribute it and/or modify | ||||||
|  |  * it under the terms of version 2 of the GNU General Public License as | ||||||
|  |  * published by the Free Software Foundation. | ||||||
|  |  * | ||||||
|  |  * This program is distributed in the hope that it will be useful, but | ||||||
|  |  * WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||||
|  |  * General Public License for more details. | ||||||
|  |  * | ||||||
|  |  * The full GNU General Public License is included in this distribution | ||||||
|  |  * in the file called COPYING. | ||||||
|  |  */ | ||||||
| #ifndef __DEVCOREDUMP_H | #ifndef __DEVCOREDUMP_H | ||||||
| #define __DEVCOREDUMP_H | #define __DEVCOREDUMP_H | ||||||
| 
 | 
 | ||||||
|  | @ -5,17 +24,62 @@ | ||||||
| #include <linux/module.h> | #include <linux/module.h> | ||||||
| #include <linux/vmalloc.h> | #include <linux/vmalloc.h> | ||||||
| 
 | 
 | ||||||
|  | #include <linux/scatterlist.h> | ||||||
|  | #include <linux/slab.h> | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * _devcd_free_sgtable - free all the memory of the given scatterlist table | ||||||
|  |  * (i.e. both pages and scatterlist instances) | ||||||
|  |  * NOTE: if two tables allocated and chained using the sg_chain function then | ||||||
|  |  * this function should be called only once on the first table | ||||||
|  |  * @table: pointer to sg_table to free | ||||||
|  |  */ | ||||||
|  | static inline void _devcd_free_sgtable(struct scatterlist *table) | ||||||
|  | { | ||||||
|  | 	int i; | ||||||
|  | 	struct page *page; | ||||||
|  | 	struct scatterlist *iter; | ||||||
|  | 	struct scatterlist *delete_iter; | ||||||
|  | 
 | ||||||
|  | 	/* free pages */ | ||||||
|  | 	iter = table; | ||||||
|  | 	for_each_sg(table, iter, sg_nents(table), i) { | ||||||
|  | 		page = sg_page(iter); | ||||||
|  | 		if (page) | ||||||
|  | 			__free_page(page); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/* then free all chained tables */ | ||||||
|  | 	iter = table; | ||||||
|  | 	delete_iter = table;	/* always points on a head of a table */ | ||||||
|  | 	while (!sg_is_last(iter)) { | ||||||
|  | 		iter++; | ||||||
|  | 		if (sg_is_chain(iter)) { | ||||||
|  | 			iter = sg_chain_ptr(iter); | ||||||
|  | 			kfree(delete_iter); | ||||||
|  | 			delete_iter = iter; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/* free the last table */ | ||||||
|  | 	kfree(delete_iter); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| #ifdef CONFIG_DEV_COREDUMP | #ifdef CONFIG_DEV_COREDUMP | ||||||
| void dev_coredumpv(struct device *dev, const void *data, size_t datalen, | void dev_coredumpv(struct device *dev, void *data, size_t datalen, | ||||||
| 		   gfp_t gfp); | 		   gfp_t gfp); | ||||||
| 
 | 
 | ||||||
| void dev_coredumpm(struct device *dev, struct module *owner, | void dev_coredumpm(struct device *dev, struct module *owner, | ||||||
| 		   const void *data, size_t datalen, gfp_t gfp, | 		   void *data, size_t datalen, gfp_t gfp, | ||||||
| 		   ssize_t (*read)(char *buffer, loff_t offset, size_t count, | 		   ssize_t (*read)(char *buffer, loff_t offset, size_t count, | ||||||
| 				   const void *data, size_t datalen), | 				   void *data, size_t datalen), | ||||||
| 		   void (*free)(const void *data)); | 		   void (*free)(void *data)); | ||||||
|  | 
 | ||||||
|  | void dev_coredumpsg(struct device *dev, struct scatterlist *table, | ||||||
|  | 		    size_t datalen, gfp_t gfp); | ||||||
| #else | #else | ||||||
| static inline void dev_coredumpv(struct device *dev, const void *data, | static inline void dev_coredumpv(struct device *dev, void *data, | ||||||
| 				 size_t datalen, gfp_t gfp) | 				 size_t datalen, gfp_t gfp) | ||||||
| { | { | ||||||
| 	vfree(data); | 	vfree(data); | ||||||
|  | @ -23,13 +87,19 @@ static inline void dev_coredumpv(struct device *dev, const void *data, | ||||||
| 
 | 
 | ||||||
| static inline void | static inline void | ||||||
| dev_coredumpm(struct device *dev, struct module *owner, | dev_coredumpm(struct device *dev, struct module *owner, | ||||||
| 	      const void *data, size_t datalen, gfp_t gfp, | 	      void *data, size_t datalen, gfp_t gfp, | ||||||
| 	      ssize_t (*read)(char *buffer, loff_t offset, size_t count, | 	      ssize_t (*read)(char *buffer, loff_t offset, size_t count, | ||||||
| 			      const void *data, size_t datalen), | 			      void *data, size_t datalen), | ||||||
| 	      void (*free)(const void *data)) | 	      void (*free)(void *data)) | ||||||
| { | { | ||||||
| 	free(data); | 	free(data); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | static inline void dev_coredumpsg(struct device *dev, struct scatterlist *table, | ||||||
|  | 				  size_t datalen, gfp_t gfp) | ||||||
|  | { | ||||||
|  | 	_devcd_free_sgtable(table); | ||||||
|  | } | ||||||
| #endif /* CONFIG_DEV_COREDUMP */ | #endif /* CONFIG_DEV_COREDUMP */ | ||||||
| 
 | 
 | ||||||
| #endif /* __DEVCOREDUMP_H */ | #endif /* __DEVCOREDUMP_H */ | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Aviya Erenfeld
						Aviya Erenfeld