UTF8 Codepoint decode and length
Rather than use narrow types, use fastest ones

// uint8_t first = (uint8_t)(*txt);
unsigned first = (uint8_t)(*txt);
// or
uint_fast8_t first = (uint8_t)(*txt);


Rather than lookup a value to shift, look up the shifted value.

// static uint8_t MSK[] = {0,0,3,4,5,0,0,0};
//  val = first &amp; (0xFF &gt;&gt; MSK[len]);

static const uint8_t FF_MSK[] = {0xFF &gt;&gt;0, 0xFF &gt;&gt;0, 0xFF &gt;&gt;3, 
    0xFF &gt;&gt;4, 0xFF &gt;&gt;5, 0xFF &gt;&gt;0, 0xFF &gt;&gt;0, 0xFF &gt;&gt;0};
val = first &amp; FF_MSK[len];


Some modern compilers can make additional optimizations if the pointers are known to not overlap - use restrict and const where applicable.

// int utf8_cp(char *txt, int32_t *ch)
int utf8_cp(const char * restrict txt, int32_t *restrict ch)


Coding the companion function would aid in testing for both functions.

int utf8_cp_encode(int32_t *ch, char *txt);


As code does not detect invalid encoding like surrogates, redundant patterns and values above max_Unicode, I see little value in handling only a subset of invalid sequences.  Either detect them all (maybe in debug mode)  or skip detection.

Suggest doing a 32-byte (or 256-byte) lookup for performance.  Profile to find optimal.

// len = (first &gt; 0) * (1 + ((first &amp; 0xC0) == 0xC0) * LEN[(first &gt;&gt; 3) &amp; 7]);
len = (first &gt; 0) * LEN_32[first &gt;&gt; 3];
// or
len = LEN_256[first];


Could extend the above to do one lookup for both the len and val.