Commit 52256637 authored by Aviya Erenfeld's avatar Aviya Erenfeld Committed by Greg Kroah-Hartman

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: default avatarAviya Erenfeld <aviya.erenfeld@intel.com>
Signed-off-by: default avatarJohannes Berg <johannes.berg@intel.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent c4a74f63
...@@ -4,6 +4,7 @@ ...@@ -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; ...@@ -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 = { ...@@ -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, ...@@ -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, ...@@ -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) ...@@ -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) ...@@ -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, ...@@ -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 @@ ...@@ -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, ...@@ -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;
......
/*
* 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 @@ ...@@ -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, ...@@ -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 */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment