diff --git a/firmware/buflib.c b/firmware/buflib.c index 2a4b4b4f14..7263f1b95d 100644 --- a/firmware/buflib.c +++ b/firmware/buflib.c @@ -102,10 +102,12 @@ #define PARANOIA_CHECK_HANDLE (1 << 1) #define PARANOIA_CHECK_BLOCK_HANDLE (1 << 2) #define PARANOIA_CHECK_CRC (1 << 3) +#define PARANOIA_CHECK_PINNING (1 << 4) /* Bitmask of enabled paranoia checks */ #define BUFLIB_PARANOIA \ (PARANOIA_CHECK_LENGTH | PARANOIA_CHECK_HANDLE | \ - PARANOIA_CHECK_BLOCK_HANDLE | PARANOIA_CHECK_CRC) + PARANOIA_CHECK_BLOCK_HANDLE | PARANOIA_CHECK_CRC | \ + PARANOIA_CHECK_PINNING) #if BUFLIB_PARANOIA & PARANOIA_CHECK_CRC # define BUFLIB_HAS_CRC @@ -122,6 +124,7 @@ enum { /* Backward indices, used to index a block end pointer as block[-bidx_XXX] */ enum { bidx_USER, /* dummy to get below fields to be 1-based */ + bidx_PIN, /* pin count */ #ifdef BUFLIB_HAS_CRC bidx_CRC, /* CRC, protects all metadata behind it */ #endif @@ -132,9 +135,9 @@ enum { * accounted for using the BSIZE field. Note that bidx_USER is not an * actual field so it is not included in the count. */ #ifdef BUFLIB_HAS_CRC -# define BUFLIB_NUM_FIELDS 5 +# define BUFLIB_NUM_FIELDS 6 #else -# define BUFLIB_NUM_FIELDS 4 +# define BUFLIB_NUM_FIELDS 5 #endif struct buflib_callbacks buflib_ops_locked = { @@ -394,7 +397,7 @@ move_block(struct buflib_context* ctx, union buflib_data* block, int shift) union buflib_data *block_end = h_entry_to_block_end(ctx, h_entry); check_block_crc(ctx, block, block_end); - if (!IS_MOVABLE(block)) + if (!IS_MOVABLE(block) || block_end[-bidx_PIN].pincount > 0) return false; int handle = ctx->handle_table - h_entry; @@ -751,6 +754,7 @@ buffer_alloc: size_t bsize = BUFLIB_NUM_FIELDS + name_len/sizeof(union buflib_data); union buflib_data *block_end = block + bsize; + block_end[-bidx_PIN].pincount = 0; block_end[-bidx_BSIZE].val = bsize; update_block_crc(ctx, block, block_end); @@ -1050,6 +1054,39 @@ buflib_shrink(struct buflib_context* ctx, int handle, void* new_start, size_t ne return true; } +void buflib_pin(struct buflib_context *ctx, int handle) +{ + if ((BUFLIB_PARANOIA & PARANOIA_CHECK_PINNING) && handle <= 0) + buflib_panic(ctx, "invalid handle pin: %d", handle); + + union buflib_data *data = handle_to_block_end(ctx, handle); + data[-bidx_PIN].pincount++; +} + +void buflib_unpin(struct buflib_context *ctx, int handle) +{ + if ((BUFLIB_PARANOIA & PARANOIA_CHECK_PINNING) && handle <= 0) + buflib_panic(ctx, "invalid handle unpin: %d", handle); + + union buflib_data *data = handle_to_block_end(ctx, handle); + if (BUFLIB_PARANOIA & PARANOIA_CHECK_PINNING) + { + if (data[-bidx_PIN].pincount == 0) + buflib_panic(ctx, "handle pin underflow: %d", handle); + } + + data[-bidx_PIN].pincount--; +} + +unsigned buflib_pin_count(struct buflib_context *ctx, int handle) +{ + if ((BUFLIB_PARANOIA & PARANOIA_CHECK_PINNING) && handle <= 0) + buflib_panic(ctx, "invalid handle: %d", handle); + + union buflib_data *data = handle_to_block_end(ctx, handle); + return data[-bidx_PIN].pincount; +} + const char* buflib_get_name(struct buflib_context *ctx, int handle) { union buflib_data *data = handle_to_block_end(ctx, handle); diff --git a/firmware/core_alloc.c b/firmware/core_alloc.c index bf2f8e8298..0374c801c1 100644 --- a/firmware/core_alloc.c +++ b/firmware/core_alloc.c @@ -104,6 +104,21 @@ bool core_shrink(int handle, void* new_start, size_t new_size) return buflib_shrink(&core_ctx, handle, new_start, new_size); } +void core_pin(int handle) +{ + buflib_pin(&core_ctx, handle); +} + +void core_unpin(int handle) +{ + buflib_unpin(&core_ctx, handle); +} + +unsigned core_pin_count(int handle) +{ + return buflib_pin_count(&core_ctx, handle); +} + const char* core_get_name(int handle) { const char *name = buflib_get_name(&core_ctx, handle); diff --git a/firmware/include/buflib.h b/firmware/include/buflib.h index 45446c3b86..d2231ab79d 100644 --- a/firmware/include/buflib.h +++ b/firmware/include/buflib.h @@ -38,6 +38,7 @@ union buflib_data intptr_t val; /* length of the block in n*sizeof(union buflib_data). Includes buflib metadata overhead. A negative value indicates block is unallocated */ + volatile unsigned pincount; /* number of pins */ struct buflib_callbacks* ops; /* callback functions for move and shrink. Can be NULL */ char* alloc; /* start of allocated memory area */ union buflib_data *handle; /* pointer to entry in the handle table. @@ -291,6 +292,25 @@ static inline void* buflib_get_data(struct buflib_context *ctx, int handle) */ bool buflib_shrink(struct buflib_context *ctx, int handle, void* newstart, size_t new_size); +/** + * Increment the pin count for a handle. When pinned the handle will not + * be moved and move callbacks will not be triggered, allowing a pointer + * to the buffer to be kept across yields or used for I/O. + * + * Note that shrink callbacks can still be invoked for pinned handles. + */ +void buflib_pin(struct buflib_context *ctx, int handle); + +/** + * Decrement the pin count for a handle. + */ +void buflib_unpin(struct buflib_context *ctx, int handle); + +/** + * Get the current pin count of a handle. Zero means the handle is not pinned. + */ +unsigned buflib_pin_count(struct buflib_context *ctx, int handle); + /** * Frees memory associated with the given handle * diff --git a/firmware/include/core_alloc.h b/firmware/include/core_alloc.h index f535fc1f6e..87246bcbd6 100644 --- a/firmware/include/core_alloc.h +++ b/firmware/include/core_alloc.h @@ -14,6 +14,9 @@ int core_alloc(const char* name, size_t size); int core_alloc_ex(const char* name, size_t size, struct buflib_callbacks *ops); int core_alloc_maximum(const char* name, size_t *size, struct buflib_callbacks *ops); bool core_shrink(int handle, void* new_start, size_t new_size); +void core_pin(int handle); +void core_unpin(int handle); +unsigned core_pin_count(int handle); int core_free(int handle); size_t core_available(void); size_t core_allocatable(void);