blob: 21aac60318ebfa034cb14a7f24e04c63eed01fd7 [file] [log] [blame]
/*
* Copyright (c) 2011 Quantenna Communications, Inc.
* All rights reserved.
*
* Create a wrapper around other bootcfg datastores which compresses on write
* and decompresses on read.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
#include "bootcfg_drv.h"
#include <qtn/bootcfg.h>
#include <common/ruby_partitions.h>
#include <common/ruby_version.h>
#include <linux/init.h>
#include <linux/zlib.h>
#include <linux/zutil.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/slab.h>
struct bootcfg_zops_data {
struct bootcfg_store_ops outer_ops;
struct bootcfg_store_ops *inner_ops;
size_t inner_store_limit;
z_stream inflate_stream;
z_stream deflate_stream;
};
static struct bootcfg_zops_data *get_zops_data(struct bootcfg_store_ops *ops)
{
return (struct bootcfg_zops_data*)ops;
}
static struct bootcfg_store_ops *get_inner_ops(struct bootcfg_store_ops *ops)
{
return get_zops_data(ops)->inner_ops;
}
int __init bootcfg_zadpt_init(struct bootcfg_store_ops *ops, size_t *store_limit)
{
struct bootcfg_zops_data *data = get_zops_data(ops);
struct bootcfg_store_ops *inner_ops = get_inner_ops(ops);
int ret = 0;
data->inflate_stream.workspace = kzalloc(zlib_inflate_workspacesize(), GFP_KERNEL);
if (data->inflate_stream.workspace == NULL) {
ret = -ENOMEM;
goto out;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
data->deflate_stream.workspace = kmalloc(zlib_deflate_workspacesize(MAX_WBITS, DEF_MEM_LEVEL),
GFP_KERNEL);
#else
data->deflate_stream.workspace = kmalloc(zlib_deflate_workspacesize(), GFP_KERNEL);
#endif
if (data->deflate_stream.workspace == NULL) {
ret = -ENOMEM;
goto out_free_inf;
}
ret = inner_ops->init(inner_ops, &data->inner_store_limit);
if (ret) {
goto out_free_both;
}
return 0;
out_free_both:
kfree(data->deflate_stream.workspace);
out_free_inf:
kfree(data->inflate_stream.workspace);
out:
kfree(ops);
return ret;
}
void __exit bootcfg_zadpt_exit(struct bootcfg_store_ops *ops)
{
struct bootcfg_zops_data *data = get_zops_data(ops);
struct bootcfg_store_ops *inner_ops = get_inner_ops(ops);
kfree(data->deflate_stream.workspace);
kfree(data->inflate_stream.workspace);
inner_ops->exit(inner_ops);
kfree(ops);
}
static int bootcfg_zadpt_read(struct bootcfg_store_ops *ops, void* buf, const size_t bytes)
{
struct bootcfg_zops_data *data = get_zops_data(ops);
struct bootcfg_store_ops *inner_ops = get_inner_ops(ops);
uint8_t *inner_buf;
uint32_t compressed_size = 0;
size_t inner_buf_size;
int ret = 0;
inner_buf_size = bytes;
if (data->inner_store_limit && data->inner_store_limit < inner_buf_size) {
inner_buf_size = data->inner_store_limit;
}
inner_buf = kzalloc(inner_buf_size, GFP_KERNEL);
if (inner_buf == NULL) {
ret = -ENOMEM;
goto out;
}
ret = inner_ops->read(inner_ops, inner_buf, sizeof(uint32_t));
if (ret) {
goto out;
}
/* get compressed size (first 4 bytes) and sanity check */
memcpy(&compressed_size, &inner_buf[0], sizeof(uint32_t));
if (compressed_size > inner_buf_size - sizeof(uint32_t)) {
ret = -ENODATA;
goto out;
}
ret = inner_ops->read(inner_ops, inner_buf, compressed_size + sizeof(uint32_t));
if (ret) {
goto out;
}
data->inflate_stream.next_in = &inner_buf[sizeof(uint32_t)];
data->inflate_stream.total_in = 0;
data->inflate_stream.avail_in = compressed_size;
data->inflate_stream.next_out = buf;
data->inflate_stream.total_out = 0;
data->inflate_stream.avail_out = bytes;
ret = zlib_inflateInit(&data->inflate_stream);
if (ret != Z_OK) {
ret = -EIO;
goto out;
}
ret = zlib_inflate(&data->inflate_stream, Z_FINISH);
if (ret != Z_STREAM_END) {
printk(KERN_ERR "%s deflate failed: ret %d\n", __FUNCTION__, ret);
ret = -ENODATA;
goto out;
}
#ifdef DEBUG
printk(KERN_DEBUG "%s zlib decompressed %ld bytes into %ld\n",
__FUNCTION__, data->inflate_stream.total_in, data->inflate_stream.total_out);
#endif
ret = 0;
out:
if (inner_buf) {
kfree(inner_buf);
}
return ret;
}
static int bootcfg_zadpt_write(struct bootcfg_store_ops *ops, const void* buf, const size_t bytes)
{
struct bootcfg_zops_data *data = get_zops_data(ops);
struct bootcfg_store_ops *inner_ops = get_inner_ops(ops);
uint8_t *inner_buf = NULL;
size_t inner_buf_size;
uint32_t compressed_size;
int ret = 0;
inner_buf_size = bytes;
if (data->inner_store_limit && data->inner_store_limit < inner_buf_size) {
inner_buf_size = data->inner_store_limit;
}
inner_buf = kzalloc(inner_buf_size, GFP_KERNEL);
if (inner_buf == NULL) {
ret = -ENOMEM;
goto out;
}
ret = zlib_deflateInit(&data->deflate_stream, 3);
if (ret != Z_OK) {
ret = -EIO;
goto out;
}
data->deflate_stream.next_in = buf;
data->deflate_stream.total_in = 0;
data->deflate_stream.avail_in = bytes;
data->deflate_stream.next_out = &inner_buf[sizeof(uint32_t)];
data->deflate_stream.total_out = 0;
data->deflate_stream.avail_out = inner_buf_size - sizeof(uint32_t);
ret = zlib_deflate(&data->deflate_stream, Z_FINISH);
if (ret != Z_STREAM_END) {
printk(KERN_ERR "%s deflate failed: ret %d\n", __FUNCTION__, ret);
ret = -ENOSPC;
goto out;
}
#ifdef DEBUG
printk(KERN_DEBUG "%s zlib compressed %ld bytes into %ld\n",
__FUNCTION__, data->deflate_stream.total_in, data->deflate_stream.total_out);
#endif
compressed_size = data->deflate_stream.total_out;
memcpy(inner_buf, &compressed_size, sizeof(compressed_size));
ret = inner_ops->write(inner_ops, inner_buf, compressed_size + sizeof(uint32_t));
out:
if (inner_buf) {
kfree(inner_buf);
}
return ret;
}
static const struct bootcfg_store_ops zapdt_outer_ops = {
.read = bootcfg_zadpt_read,
.write = bootcfg_zadpt_write,
.init = bootcfg_zadpt_init,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
.exit = bootcfg_zadpt_exit,
#else
.exit = __devexit_p(bootcfg_zadpt_exit),
#endif
};
struct bootcfg_store_ops *bootcfg_compression_adapter(struct bootcfg_store_ops *raw_accessor)
{
struct bootcfg_zops_data *z_ops = kmalloc(sizeof(*z_ops), GFP_KERNEL);
if (z_ops == NULL) {
return NULL;
}
z_ops->outer_ops = zapdt_outer_ops;
z_ops->inner_ops = raw_accessor;
return &z_ops->outer_ops;
}