summaryrefslogtreecommitdiff
path: root/drivers/md/dm-pcache/pcache_internal.h
blob: d427e534727cefc29d123653d867ffd9632d99f6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef _PCACHE_INTERNAL_H
#define _PCACHE_INTERNAL_H

#include <linux/delay.h>
#include <linux/crc32c.h>

#define pcache_err(fmt, ...)							\
	pr_err("dm-pcache: %s:%u " fmt, __func__, __LINE__, ##__VA_ARGS__)
#define pcache_info(fmt, ...)							\
	pr_info("dm-pcache: %s:%u " fmt, __func__, __LINE__, ##__VA_ARGS__)
#define pcache_debug(fmt, ...)							\
	pr_debug("dm-pcache: %s:%u " fmt, __func__, __LINE__, ##__VA_ARGS__)

#define PCACHE_KB			(1024ULL)
#define PCACHE_MB			(1024 * PCACHE_KB)

/* Maximum number of metadata indices */
#define PCACHE_META_INDEX_MAX		2

#define PCACHE_CRC_SEED			0x3B15A
/*
 * struct pcache_meta_header - PCACHE metadata header structure
 * @crc: CRC checksum for validating metadata integrity.
 * @seq: Sequence number to track metadata updates.
 * @version: Metadata version.
 * @res: Reserved space for future use.
 */
struct pcache_meta_header {
	__u32 crc;
	__u8  seq;
	__u8  version;
	__u16 res;
};

/*
 * pcache_meta_crc - Calculate CRC for the given metadata header.
 * @header: Pointer to the metadata header.
 * @meta_size: Size of the metadata structure.
 *
 * Returns the CRC checksum calculated by excluding the CRC field itself.
 */
static inline u32 pcache_meta_crc(struct pcache_meta_header *header, u32 meta_size)
{
	return crc32c(PCACHE_CRC_SEED, (void *)header + 4, meta_size - 4);
}

/*
 * pcache_meta_seq_after - Check if a sequence number is more recent, accounting for overflow.
 * @seq1: First sequence number.
 * @seq2: Second sequence number.
 *
 * Determines if @seq1 is more recent than @seq2 by calculating the signed
 * difference between them. This approach allows handling sequence number
 * overflow correctly because the difference wraps naturally, and any value
 * greater than zero indicates that @seq1 is "after" @seq2. This method
 * assumes 8-bit unsigned sequence numbers, where the difference wraps
 * around if seq1 overflows past seq2.
 *
 * Returns:
 *   - true if @seq1 is more recent than @seq2, indicating it comes "after"
 *   - false otherwise.
 */
static inline bool pcache_meta_seq_after(u8 seq1, u8 seq2)
{
	return (s8)(seq1 - seq2) > 0;
}

/*
 * pcache_meta_find_latest - Find the latest valid metadata.
 * @header: Pointer to the metadata header.
 * @meta_size: Size of each metadata block.
 *
 * Finds the latest valid metadata by checking sequence numbers. If a
 * valid entry with the highest sequence number is found, its pointer
 * is returned. Returns NULL if no valid metadata is found.
 */
static inline void __must_check *pcache_meta_find_latest(struct pcache_meta_header *header,
					u32 meta_size, u32 meta_max_size,
					void *meta_ret)
{
	struct pcache_meta_header *meta, *latest = NULL;
	u32 i, seq_latest = 0;
	void *meta_addr;

	meta = meta_ret;

	for (i = 0; i < PCACHE_META_INDEX_MAX; i++) {
		meta_addr = (void *)header + (i * meta_max_size);
		if (copy_mc_to_kernel(meta, meta_addr, meta_size)) {
			pcache_err("hardware memory error when copy meta");
			return ERR_PTR(-EIO);
		}

		/* Skip if CRC check fails, which means corrupted */
		if (meta->crc != pcache_meta_crc(meta, meta_size))
			continue;

		/* Update latest if a more recent sequence is found */
		if (!latest || pcache_meta_seq_after(meta->seq, seq_latest)) {
			seq_latest = meta->seq;
			latest = (void *)header + (i * meta_max_size);
		}
	}

	if (!latest)
		return NULL;

	if (copy_mc_to_kernel(meta_ret, latest, meta_size)) {
		pcache_err("hardware memory error");
		return ERR_PTR(-EIO);
	}

	return latest;
}

#endif /* _PCACHE_INTERNAL_H */