| /* |
| * 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; |
| } |
| |