Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CRC feature implementation #5669

Closed
wants to merge 4 commits into from

Conversation

deepikabhavnani
Copy link

@deepikabhavnani deepikabhavnani commented Dec 6, 2017

CRC class implementation

CRC class BaseCRC.h is created to support hardware/software CRCs

BitwiseCRC.h contains software CRC implementation for bitwise CRC computation which will be somewhat slow compared to other techniques as bit by bit computation is performed in software.

FastCRC.h contains software CRC implementation using lookup table method. Table is generated during init step.

HalfByteCRC.h constains software implementation for CRC32 using 16-entry technique to have limited table size (64bytes) and is fast compared to bitwise CRC computation. Table is generated runtime during init call.

Usage example:
https://gist.github.com/deepikabhavnani/0dacf5572c0b4d87b8fbfbcfd9246a95


#define GET_DATA_SIZE(x) ((x) <= 8 ? 1 : ((x) <= 16 ? 2 : 4))
#define TOP_BIT(x) ((x) < 8 ? (1u << 7) : (1u << (x - 1)))
#define DATA_MASK(x) ((1u << x) - 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can these defines be moved into the C++ file?

Copy link
Author

@deepikabhavnani deepikabhavnani Dec 6, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, if we had any :-) . Software CRC classes are template type hence we have just header file for them. Also, defines are generic for Slow/Fast CRC and even hardware CRC driver can use these defines.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we keep this as defines (I would do local inline functions helpers to get this would have the same result and might not pollute symbols), then use prefix as for above enums (CRC_).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two uses of x that are not enclosed in parentheses - could you add those?

case CRC_7BIT_SD:
_polynomial = 0x9; // x7+x3+1;
_width = 7;
break;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just provide polynomial/width/initial_value/reflect_data/reflect_remainder/final_xor all as constructor parameters with reasonable defaults?

If you want to get creative, you could do something like this:

// Presets for standard 32 bit CRC
#define CRC_32BIT 0x04C11DB7, 32, ~0, true, true, ~0

// Declare 32 bit CRC
SlowCRC<uint32_t> crc(CRC_32BIT);

// Declare non-standard 32 bit CRC
SlowCRC<uint32_t> crc(0x04C11DB7, 32, 0);

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because not all of them are verified, I don't want to give impression that all CRC computations are implemented without verifying each of them. Ones which are used most are implemented and verified. Deviation from standard ones will give incorrect result.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deviation from standard ones will give incorrect result.

They're all backed by the same algorithm though aren't they?

You could still say only the CRCs with matching defines are supported, and assert for invalid configurations in the constructor.

/** @{*/
#include <stdint.h>

typedef uint64_t data_size_t;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be crc_data_size_t? or just crc_t?

data_size_t is ambiguous

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

crc_data_size_t 👍 . Will change it

*
* @param buffer Data bytes
* @param size Size of data
* @param crc CRC
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought: you should document what the initial state of crc is expected to be.

Can I crc data without storing it all in memory at once? This is a requirement for littlefs.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initial state of CRC is not picked from this argument, we only return computed CRC , Initial value is don;t care.
Inital XOR values for CRC, vary based on the CRC algorithm selected, which is set as part of constructor.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think we could have a compute_partial or something similar to continue a CRC without initializing the value?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partial CRC, yes we can have support for that in future. But partial CRC computation should be separate API and we will need proper locking mechanism with that. Partial CRC method is used to compute entire CRC in parts in parallel.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh no, not for parallel computation. Just for computing a CRC when the data being CRC'd doesn't fit in RAM. The data can be streamed in, but only if the CRC can be computed partially.

This can be another pr if you'd like, it would just be needed before this class can be adopted everywhere.

#include "mbed.h"

template <typename T>
class FastCRC : public CRC
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be worth documenting this template parameter. I'm not entirely sure what value it adds to the class (the internal table uses T as elements? Does the resulting CRC size depend on T or the polynomial argument?)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resulting CRC size will always be uint32_t (masked based on CRC width). Kept it generic in CRC class

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be a template then? you can extend an instantiated template:

class FastCRC : public CRC<uint32_t>

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRC class is generic and will be used for hardware CRC as well. If you check hardware CRC implementation of target devices, they all return 32-bit CRC (masked) based on polynomial. Could be because of C implementation, but in order to have consistent behavior it would be good to not have template in base class.

Any inputs from HAL team?

@geky
Copy link
Contributor

geky commented Dec 6, 2017

Is there a CRC implementation that sits between FastCRC and SlowCRC in terms of ROM vs runtime?

I'm curious which CRC option best matches littlefs's:
https://github.com/ARMmbed/mbed-os/blob/master/features/filesystem/littlefs/littlefs/lfs_util.c#L21-L35

Also is it safe to assume this opens up the opportunity to add hardware accelerated CRC?

@deepikabhavnani
Copy link
Author

deepikabhavnani commented Dec 7, 2017

Intermediate version is to use smaller tables (16-entry table) if we have space constraints. The method which you have in littlefs.
http://create.stephan-brumme.com/crc32/

Copy link
Contributor

@0xc0170 0xc0170 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@deepikabhavnani We should ask to review this addition with mbedtls team. I'll tag them here

@@ -0,0 +1,102 @@
/* mbed Microcontroller Library
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

location - features/crc ? this is not part of platform folder? - just thinking why feature/ location was chosen

Copy link
Author

@deepikabhavnani deepikabhavnani Dec 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current CRC use case is block device and filesystem, hence selected feature.
In future mbed_crc.h class can be used as base class for hardware CRC, hence we can have it HAL layer as well.
Inputs from core+hal team will be appreciated for this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would assume it should go to drivers/ straight away.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move entire CRC implementation to drivers? Or just mbed_crc.h.
Naming convention in driver folder does not have mbed prefix. Should I rename file to be CRC.h?
If everything is moved inside driver, should we have folder structure for software implementation or everything should be at same level?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say all CRC files and we should match the convention for the drivers and I don't think we need directory structure for it.


#define GET_DATA_SIZE(x) ((x) <= 8 ? 1 : ((x) <= 16 ? 2 : 4))
#define TOP_BIT(x) ((x) < 8 ? (1u << 7) : (1u << (x - 1)))
#define DATA_MASK(x) ((1u << x) - 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we keep this as defines (I would do local inline functions helpers to get this would have the same result and might not pollute symbols), then use prefix as for above enums (CRC_).

#define MBED_FAST_CRC_H

#include "mbed_crc.h"
#include "mbed.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be included within our code base (include everything is not an answer to what symbol is missing), please include what is needed here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch 👍

Sounds like we should have a script to catch these... 🤔

*
* @return 0 on success, negative error code on failure
*/
virtual int32_t deinit() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style as in the above file { on the new line for body functions

@0xc0170
Copy link
Contributor

0xc0170 commented Dec 7, 2017

@mazimkhan @hanno-arm you might be interested in this addition, please review if that is the case

@ciarmcom
Copy link
Member

ciarmcom commented Dec 7, 2017

ARM Internal Ref: IOTSSL-1938

*/

#ifndef MBED_HALF_BYTE_CRC_H
#define MBED_HALF_BYTE_CRC_H
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@geky - Please check if this implementation will suffice the CRC32 requirement for littlefs.
Tested with: https://gist.github.com/deepikabhavnani/ff6fc74c4150cff36d398c1d2a4e7f59

Copy link
Contributor

@geky geky Dec 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me! Only thought is that the name HalfByteCRC is a bit confusing. What about MediumCRC? Another thought, what if we renamed SlowCRC to SmallCRC. Just tossing out ideas, it's up to you.

other terribles names:

  • ModestCRC
  • PracticalCRC
  • KindaFastKindaSlowCRC
  • BestCRC

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, thinking more about it. Maybe we should just leave out HalfByteCRC and force LittleFS to use FastCRC. It's probably better to have fewer options so there's more chances for the implementation to get reused.

We can also provide a config option for LittleFS to use SlowCRC (SmallCRC?) in the cases the size penalty is too much.

Thoughts?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name HalfByteCRC is a bit confusing

Naming would be good based on speed/technique.
Since we have so many different variations we can change from fast/slow to technique based.
Fast CRC - lookupTable or StandardTable CRC
Slow CRC - bitwiseCRC or tableless CRC.
HalfByteCRC - Half-byte/Nibble/16-byte entry table are different options for

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, thinking more about it. Maybe we should just leave out HalfByteCRC and force LittleFS to use FastCRC

We can have default as FastCRC and switch to SlowCRC option in targets.json. Selection will be one-time mostly based on target and not application. Correct?
No harm in providing half-byte option as well, we just need to finalize one default.

@simonbutcher
Copy link
Contributor

Hi @0xc0170,

This isn't security or crypto related, so doesn't require a review from @hanno-arm or @mazimkhan, although I don't object to them reviewing it.

Could you please remove the TLS label? Thanks.

MBED_ASSERT(buffer != NULL);

uint8_t *data = static_cast<uint8_t *>(buffer);
uint32_t remainder = p_crc == 0x0 ? _inital_value : ~p_crc;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p_crc == 0x0 might be a valid intermediary value for the crc, this would break if that lined up with a compute_partial block. You can expect the user to either initialize it themselves, or call compute first

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Asking user to set initial value will be better then calling compute first. Will update this.

uint8_t *data = static_cast<uint8_t *>(buffer);
uint32_t remainder = _inital_value;

for (crc_data_size_t byte = 0; byte < size; byte++) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can compute just call compute_partial now?

*/
virtual int32_t compute(void *buffer, crc_data_size_t size, uint32_t *crc)
{
MBED_ASSERT(crc != NULL);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that these aren't templates, could the implementations be moved back into C++ files?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FastCRC implementation is template based

remainder = _crc_table[((remainder ^ (data[byte] >> 4)) & 0x0F)] ^ (remainder >> 4);
}

*crc = (remainder ^ _final_xor);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

XORing with _final_xor will also cause problems in partial computes. Not sure the best way to handle this. You could have compute_partial_start and compute_partial_stop for the initializing/finalizing. In LittleFS's case it's just handling the initializing/finalizing itself so you could also just get by without the initializing/finalizing for now.

private:
uint32_t _polynomial;
uint8_t _width;
T _crc_table[256];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the CRC table be same for same polynomial? In that case, have you thought of sharing the table for same polynomial types across multiple instances. That can help save lot of memory and will avoid computing the table for same polynomial type in init. May be the assumption here is to use same CRC object across the module(For E.g.: fs, block device), in that case should we protect the interfaces for thread-safety?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having common CRC table across modules will require proper thread-safety, but I am not sure if any application will have this use case. Can however think of it in future

* @note CRC calculations are done byte wise.
*/
virtual int32_t compute(void *buffer, data_size_t size, uint32_t *crc) {
MBED_ASSERT(crc != NULL);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't MBED_ASSERTs removed for non-debug builds? In that case its better to return an error when you get an invalid parameter instead of working with invalid data(works for release builds).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case its better to return an error when you get an invalid parameter

== more code size

In this case we'll hit a hard fault either way, which is probably clear enough as to what went wrong IMO.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As of now hitting Hard Fault will put the application into an unrecoverable state, where as if we return an error the application can handle that more gracefully, like logging/reporting the error to the user. And hitting hardfault is not a scenario easily debuggable in a customer/deployed scenario and without a debugger. But I do agree that returning error code will slightly increase the code size.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this case is a usage error. The correct way to handle this error is to not pass in NULL in the first place.

Point taken for making it easier to debug in the customer scenario.

But I have a hard time imagining the case where someone propagates error codes correctly, but passes NULL into this function.


Just food for thought, I don't think this conversation should block this pr

Copy link
Member

@pan- pan- left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to use pre initialized look up tables for FastCRC and HalfByteCRC (in addition of existing methods) ? It would help users to deport memory cost of Fast CRC from RAM to ROM.

I'm personally not completely sold on the template + polymorphic approach taken on FastCRC and SlowCRC: In the parent class, the CRC is always passed as an uint32_t value but in the template class it is also the template type that parametrize the class; both might differ from one to another.


/** CRC Polynomial type
*
* Differnet polynomials supported
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: Different

/** @{*/
#include <stdint.h>

typedef uint64_t crc_data_size_t;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be part of the CRC class.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean add this define to every derived class?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that context derived classes would have access to the typedef; no reason to define it in every class. Inside CRC is enough.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that context derived classes would have access to the typedef; no reason to define it in every class. Inside CRC is enough.

/** \addtogroup crc */
/** @{*/
#include <stdint.h>

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice if the CRC declarations were protected by a namespace.

CRC_32 = 32,
} crc_width_t;

#define CRC_GET_DATA_SIZE(x) ((x) <= 8 ? 1 : ((x) <= 16 ? 2 : 4))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to use functions rather than macros and document those ?

*
* @return Polynomial value
*/
virtual uint32_t get_polynomial(void) const = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be interesting to add a typedef for the polynomial.

* @param crc CRC value (intermediate CRC)
* @return 0 on success or a negative error code on failure
*/
virtual int32_t compute_partial(void *buffer, crc_data_size_t size, uint32_t *crc)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buffer may be const; the function itself too.

* This API is used to compute final chunk of CRC, else user should perform
* the final XOR operation on intermediate computed value to get correct CRC.
*
* @param buffer Data bytes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buffer, size and p_crc are not part of the parameters.

* @param buffer Data bytes
* @param size Size of data
* @param p_crc Previous CRC to continue CRC computation, should be 0 for first part
* @param crc CRC result
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you indicate that CRC is an in/out parameter ?

* @param p_crc Previous CRC to continue CRC computation, should be 0 for first part
* @param crc CRC result
*/
virtual void compute_partial_stop(uint32_t *crc)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function may be const.


/** Templated FastCRC Class
*
* @note typename must be consistent with polynomial length.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be better to parametrize the template class on crc_polynomial_t ?
That would avoid user mistakes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I would assume it should.

}
}

FastCRC::~FastCRC() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{ new line

deinit();
}

int32_t FastCRC::init(void)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the intention with init/deinit methods, instead of doing this in ctor/dtor (dtor does call deinit, but init is postponed).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes init is not mandatory for all techniques, we create table during init and allocate memory, which may result into error.
Also, some hardware CRCs require special init.

* @param buffer Data bytes
* @param size Size of data
* @param crc CRC value is intermediate CRC value filled by API.
* @return 0 on success or a negative error code on failure
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we define negative error codes, or -1 does it all?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-1 is the only error code in Software CRC implementation. However, I can use "Not supported" as additional error code, if we have common error codes, but nothing special at present.

1. Added default CRC case RawCRC
2. Renamed SlowCRC to BitwiseCRC class
3. Resolved doxygen errors
4. Resolved name clashes with different targets
@deepikabhavnani
Copy link
Author

deepikabhavnani commented Jan 4, 2018

@bulislaw @0xc0170 @geky @pan- @SenRamakri - Please approve if all your comments are addressed. Thanks

CRC final computation and shift operation was done in compute partial API,
which resulted in incorrect results for 8-bit CRC.
Renamed 32-bit CRC, to 32-bit ANSI CRC to have more 32-bit CRC types in future.
@0xc0170
Copy link
Contributor

0xc0170 commented Jan 9, 2018

/morph build

@mbed-ci
Copy link

mbed-ci commented Jan 9, 2018

Build : SUCCESS

Build number : 827
Build artifacts/logs : http://mbed-os.s3-website-eu-west-1.amazonaws.com/?prefix=builds/5669/

Triggering tests

/morph test
/morph uvisor-test
/morph export-build

@mbed-ci
Copy link

mbed-ci commented Jan 9, 2018

@mbed-ci
Copy link

mbed-ci commented Jan 9, 2018

Test : FAILURE

Build number : 667
Test logs :http://mbed-os-logs.s3-website-us-west-1.amazonaws.com/?prefix=logs/5669/667

@cmonr
Copy link
Contributor

cmonr commented Feb 1, 2018

@deepikabhavnani Will 5911 supersede this PR?

@deepikabhavnani
Copy link
Author

@cmonr - yes it will. We can close this once 5911 is approved.

@deepikabhavnani
Copy link
Author

Closing this, as PR #5911 will be merged after approvals

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.