[alsa-devel] [PATCH 1/2] ASoC: soc-cache: block based rbtree compression
This patch prepares the ground for the actual rbtree optimization patch which will save a pointer to the last accessed register block that was used in either the read() or write() functions.
Each node manages a block of up to RBTREE_BLOCK_NUM registers. There can be no two nodes with overlapping blocks. Currently there is no check in the code to scream in case that ever happens. Each block has a base and top register, all others lie in between these registers. Note that variable length blocks aren't supported. So if you have an interval [8, 15] and only some of those registers actually exist on the device, the block will have the non-existent registers as zero. There is also no way of reporting that any of those non-existent registers were accessed/modified.
The larger the block size, the more probable it is that one of the managed registers is non-zero, and therefore the node will need to be allocated at initialization time and waste space.
If register N is accessed and it is not part of any of the current blocks in the rbtree, a new node is created with a base register which is floor(N / RBTREE_BLOCK_NUM) * RBTREE_BLOCK_NUM and a top register as base_register + RBTREE_BLOCK_NUM - 1. All other registers in the block are initialized as expected and the node is inserted into the tree.
Signed-off-by: Dimitris Papastamos dp@opensource.wolfsonmicro.com --- sound/soc/soc-cache.c | 177 +++++++++++++++++++++++++++++++++++-------------- 1 files changed, 128 insertions(+), 49 deletions(-)
diff --git a/sound/soc/soc-cache.c b/sound/soc/soc-cache.c index a217db2..c518b6e 100644 --- a/sound/soc/soc-cache.c +++ b/sound/soc/soc-cache.c @@ -593,32 +593,58 @@ static unsigned int snd_soc_get_cache_val(const void *base, unsigned int idx, return -1; }
-struct snd_soc_rbtree_node { - struct rb_node node; +struct snd_soc_rbtree_reg_blk { unsigned int reg; unsigned int value; unsigned int defval; } __attribute__ ((packed));
+#define RBTREE_BLOCK_NUM 32 +struct snd_soc_rbtree_node { + struct rb_node node; + struct snd_soc_rbtree_reg_blk block[RBTREE_BLOCK_NUM]; + unsigned int blklen; +} __attribute__ ((packed)); + struct snd_soc_rbtree_ctx { struct rb_root root; };
+static inline int snd_soc_rbtree_block_count(void) +{ + return RBTREE_BLOCK_NUM; +} + +static inline struct snd_soc_rbtree_reg_blk *snd_soc_rbtree_base_block( + struct snd_soc_rbtree_node *rbnode) +{ + return &rbnode->block[0]; +} + +static inline struct snd_soc_rbtree_reg_blk *snd_soc_rbtree_top_block( + struct snd_soc_rbtree_node *rbnode) +{ + return &rbnode->block[snd_soc_rbtree_block_count() - 1]; +} + static struct snd_soc_rbtree_node *snd_soc_rbtree_lookup( struct rb_root *root, unsigned int reg) { struct rb_node *node; struct snd_soc_rbtree_node *rbnode; + struct snd_soc_rbtree_reg_blk *baseblk, *topblk;
node = root->rb_node; while (node) { rbnode = container_of(node, struct snd_soc_rbtree_node, node); - if (rbnode->reg < reg) - node = node->rb_left; - else if (rbnode->reg > reg) - node = node->rb_right; - else + baseblk = snd_soc_rbtree_base_block(rbnode); + topblk = snd_soc_rbtree_top_block(rbnode); + if (reg >= baseblk->reg && reg <= topblk->reg) return rbnode; + else if (reg > topblk->reg) + node = node->rb_right; + else if (reg < baseblk->reg) + node = node->rb_left; }
return NULL; @@ -629,19 +655,28 @@ static int snd_soc_rbtree_insert(struct rb_root *root, { struct rb_node **new, *parent; struct snd_soc_rbtree_node *rbnode_tmp; + struct snd_soc_rbtree_reg_blk *baseblk_tmp, *topblk_tmp; + struct snd_soc_rbtree_reg_blk *baseblk;
parent = NULL; new = &root->rb_node; while (*new) { rbnode_tmp = container_of(*new, struct snd_soc_rbtree_node, node); + /* base and top blocks of the current rbnode */ + baseblk_tmp = snd_soc_rbtree_base_block(rbnode_tmp); + topblk_tmp = snd_soc_rbtree_top_block(rbnode_tmp); + /* base block of the rbnode to be added */ + baseblk = snd_soc_rbtree_base_block(rbnode); parent = *new; - if (rbnode_tmp->reg < rbnode->reg) - new = &((*new)->rb_left); - else if (rbnode_tmp->reg > rbnode->reg) - new = &((*new)->rb_right); - else + /* if this block has already been inserted, just return */ + if (baseblk->reg >= baseblk_tmp->reg && + baseblk->reg <= topblk_tmp->reg) return 0; + else if (baseblk->reg > topblk_tmp->reg) + new = &((*new)->rb_right); + else if (baseblk->reg < baseblk_tmp->reg) + new = &((*new)->rb_left); }
/* insert the node into the rbtree */ @@ -656,26 +691,31 @@ static int snd_soc_rbtree_cache_sync(struct snd_soc_codec *codec) struct snd_soc_rbtree_ctx *rbtree_ctx; struct rb_node *node; struct snd_soc_rbtree_node *rbnode; - unsigned int val; + struct snd_soc_rbtree_reg_blk *regblk; + unsigned int val, i; int ret;
rbtree_ctx = codec->reg_cache; + /* for each node sync all its blocks */ for (node = rb_first(&rbtree_ctx->root); node; node = rb_next(node)) { rbnode = rb_entry(node, struct snd_soc_rbtree_node, node); - if (rbnode->value == rbnode->defval) - continue; - WARN_ON(codec->writable_register && - codec->writable_register(codec, rbnode->reg)); - ret = snd_soc_cache_read(codec, rbnode->reg, &val); - if (ret) - return ret; - codec->cache_bypass = 1; - ret = snd_soc_write(codec, rbnode->reg, val); - codec->cache_bypass = 0; - if (ret) - return ret; - dev_dbg(codec->dev, "Synced register %#x, value = %#x\n", - rbnode->reg, val); + for (i = 0; i < rbnode->blklen; ++i) { + regblk = &rbnode->block[i]; + if (regblk->value == regblk->defval) + continue; + WARN_ON(codec->writable_register && + codec->writable_register(codec, regblk->reg)); + ret = snd_soc_cache_read(codec, regblk->reg, &val); + if (ret) + return ret; + codec->cache_bypass = 1; + ret = snd_soc_write(codec, regblk->reg, val); + codec->cache_bypass = 0; + if (ret) + return ret; + dev_dbg(codec->dev, "Synced register %#x, value = %#x\n", + regblk->reg, val); + } }
return 0; @@ -686,27 +726,40 @@ static int snd_soc_rbtree_cache_write(struct snd_soc_codec *codec, { struct snd_soc_rbtree_ctx *rbtree_ctx; struct snd_soc_rbtree_node *rbnode; + struct snd_soc_rbtree_reg_blk *regblk; + struct snd_soc_rbtree_reg_blk *baseblk; + unsigned int base_reg; + int blkcount, i;
rbtree_ctx = codec->reg_cache; rbnode = snd_soc_rbtree_lookup(&rbtree_ctx->root, reg); if (rbnode) { - if (rbnode->value == value) + baseblk = snd_soc_rbtree_base_block(rbnode); + regblk = &rbnode->block[reg - baseblk->reg]; + if (regblk->value == value) return 0; - rbnode->value = value; + regblk->value = value; } else { /* bail out early, no need to create the rbnode yet */ if (!value) return 0; /* - * for uninitialized registers whose value is changed - * from the default zero, create an rbnode and insert - * it into the tree. + * if the value of any of the uninitialized registers in a + * block is changed from the default zero, create an rbnode + * and insert it into the tree. Make sure to initialize all + * the registers in the block. */ rbnode = kzalloc(sizeof *rbnode, GFP_KERNEL); if (!rbnode) return -ENOMEM; - rbnode->reg = reg; - rbnode->value = value; + blkcount = snd_soc_rbtree_block_count(); + base_reg = (reg / blkcount) * blkcount; + for (i = 0; i < blkcount; ++i) { + regblk = &rbnode->block[i]; + regblk->reg = base_reg + i; + if (regblk->reg == reg) + regblk->value = value; + } snd_soc_rbtree_insert(&rbtree_ctx->root, rbnode); }
@@ -718,11 +771,13 @@ static int snd_soc_rbtree_cache_read(struct snd_soc_codec *codec, { struct snd_soc_rbtree_ctx *rbtree_ctx; struct snd_soc_rbtree_node *rbnode; + struct snd_soc_rbtree_reg_blk *baseblk;
rbtree_ctx = codec->reg_cache; rbnode = snd_soc_rbtree_lookup(&rbtree_ctx->root, reg); if (rbnode) { - *value = rbnode->value; + baseblk = snd_soc_rbtree_base_block(rbnode); + *value = rbnode->block[reg - baseblk->reg].value; } else { /* uninitialized registers default to 0 */ *value = 0; @@ -762,10 +817,13 @@ static int snd_soc_rbtree_cache_init(struct snd_soc_codec *codec) { struct snd_soc_rbtree_node *rbtree_node; struct snd_soc_rbtree_ctx *rbtree_ctx; - unsigned int val; + unsigned int reg, val; unsigned int word_size; - int i; + int i, j; int ret; + int blkcount; + int numblocks; + int flag;
codec->reg_cache = kmalloc(sizeof *rbtree_ctx, GFP_KERNEL); if (!codec->reg_cache) @@ -777,28 +835,49 @@ static int snd_soc_rbtree_cache_init(struct snd_soc_codec *codec) if (!codec->reg_def_copy) return 0;
- /* - * populate the rbtree with the initialized registers. All other - * registers will be inserted when they are first modified. - */ word_size = codec->driver->reg_word_size; - for (i = 0; i < codec->driver->reg_cache_size; ++i) { - val = snd_soc_get_cache_val(codec->reg_def_copy, i, word_size); - if (!val) + blkcount = snd_soc_rbtree_block_count(); + numblocks = (codec->driver->reg_cache_size - 1 + blkcount) / blkcount; + for (i = 0; i < numblocks; ++i) { + /* check if a block is comprised of uninitialized registers only */ + for (j = 0, flag = 0; j < blkcount; ++j) { + reg = i * blkcount + j; + if (reg >= codec->driver->reg_cache_size) + break; + val = snd_soc_get_cache_val(codec->reg_def_copy, + reg, word_size); + if (val) { + flag = 1; + break; + } + } + /* ignore this block until one of its registers is modified */ + if (!flag) continue; rbtree_node = kzalloc(sizeof *rbtree_node, GFP_KERNEL); if (!rbtree_node) { ret = -ENOMEM; - snd_soc_cache_exit(codec); - break; + goto err; + } + /* initialize the register block */ + for (j = 0; j < blkcount; ++j) { + reg = i * blkcount + j; + if (reg >= codec->driver->reg_cache_size) + break; + val = snd_soc_get_cache_val(codec->reg_def_copy, reg, word_size); + rbtree_node->block[j].reg = reg; + rbtree_node->block[j].value = val; + rbtree_node->block[j].defval = val; } - rbtree_node->reg = i; - rbtree_node->value = val; - rbtree_node->defval = val; + rbtree_node->blklen = j; snd_soc_rbtree_insert(&rbtree_ctx->root, rbtree_node); }
return 0; + +err: + snd_soc_cache_exit(codec); + return ret; }
#ifdef CONFIG_SND_SOC_CACHE_LZO
Whenever we are doing a read or a write through the rbtree code, we'll cache the a pointer to the register block. To avoid looking up the register everytime we do a read or a write, we first check if it can be found in the cached register block, otherwise we go through the slow path and in the end we cache the pointer to the register block.
Signed-off-by: Dimitris Papastamos dp@opensource.wolfsonmicro.com --- sound/soc/soc-cache.c | 34 ++++++++++++++++++++++++++++++++-- 1 files changed, 32 insertions(+), 2 deletions(-)
diff --git a/sound/soc/soc-cache.c b/sound/soc/soc-cache.c index c518b6e..09048ea 100644 --- a/sound/soc/soc-cache.c +++ b/sound/soc/soc-cache.c @@ -608,6 +608,7 @@ struct snd_soc_rbtree_node {
struct snd_soc_rbtree_ctx { struct rb_root root; + struct snd_soc_rbtree_node *cached_rbnode; };
static inline int snd_soc_rbtree_block_count(void) @@ -727,11 +728,25 @@ static int snd_soc_rbtree_cache_write(struct snd_soc_codec *codec, struct snd_soc_rbtree_ctx *rbtree_ctx; struct snd_soc_rbtree_node *rbnode; struct snd_soc_rbtree_reg_blk *regblk; - struct snd_soc_rbtree_reg_blk *baseblk; + struct snd_soc_rbtree_reg_blk *baseblk, *topblk; unsigned int base_reg; int blkcount, i;
rbtree_ctx = codec->reg_cache; + /* handle cached write */ + rbnode = rbtree_ctx->cached_rbnode; + if (rbnode) { + baseblk = snd_soc_rbtree_base_block(rbnode); + topblk = snd_soc_rbtree_top_block(rbnode); + if (reg >= baseblk->reg && reg <= topblk->reg) { + regblk = &rbnode->block[reg - baseblk->reg]; + if (regblk->value == value) + return 0; + regblk->value = value; + return 0; + } + } + /* if not cached, look it up in the rbtree and cache it */ rbnode = snd_soc_rbtree_lookup(&rbtree_ctx->root, reg); if (rbnode) { baseblk = snd_soc_rbtree_base_block(rbnode); @@ -739,6 +754,7 @@ static int snd_soc_rbtree_cache_write(struct snd_soc_codec *codec, if (regblk->value == value) return 0; regblk->value = value; + rbtree_ctx->cached_rbnode = rbnode; } else { /* bail out early, no need to create the rbnode yet */ if (!value) @@ -761,6 +777,7 @@ static int snd_soc_rbtree_cache_write(struct snd_soc_codec *codec, regblk->value = value; } snd_soc_rbtree_insert(&rbtree_ctx->root, rbnode); + rbtree_ctx->cached_rbnode = rbnode; }
return 0; @@ -771,13 +788,25 @@ static int snd_soc_rbtree_cache_read(struct snd_soc_codec *codec, { struct snd_soc_rbtree_ctx *rbtree_ctx; struct snd_soc_rbtree_node *rbnode; - struct snd_soc_rbtree_reg_blk *baseblk; + struct snd_soc_rbtree_reg_blk *baseblk, *topblk;
rbtree_ctx = codec->reg_cache; + /* handle cached read */ + rbnode = rbtree_ctx->cached_rbnode; + if (rbnode) { + baseblk = snd_soc_rbtree_base_block(rbnode); + topblk = snd_soc_rbtree_top_block(rbnode); + if (reg >= baseblk->reg && reg <= topblk->reg) { + *value = rbnode->block[reg - baseblk->reg].value; + return 0; + } + } + /* if not cached, look it up in the rbtree and cache it */ rbnode = snd_soc_rbtree_lookup(&rbtree_ctx->root, reg); if (rbnode) { baseblk = snd_soc_rbtree_base_block(rbnode); *value = rbnode->block[reg - baseblk->reg].value; + rbtree_ctx->cached_rbnode = rbnode; } else { /* uninitialized registers default to 0 */ *value = 0; @@ -831,6 +860,7 @@ static int snd_soc_rbtree_cache_init(struct snd_soc_codec *codec)
rbtree_ctx = codec->reg_cache; rbtree_ctx->root = RB_ROOT; + rbtree_ctx->cached_rbnode = NULL;
if (!codec->reg_def_copy) return 0;
participants (1)
-
Dimitris Papastamos