xref: /freebsd/sys/geom/zoned/g_zoned.h (revision 48efb6c4eb9febc1c8c50d13a794e9165af71930)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2026 voidanix <voidanix@FreeBSD.org>
5  */
6 
7 #ifndef	_G_ZONED_H_
8 #define	_G_ZONED_H_
9 
10 #include <sys/endian.h>
11 
12 #define	G_ZONED_CLASS_NAME	"ZONED"
13 #define	G_ZONED_MAGIC		"GEOM::ZONED"
14 #define	G_ZONED_TABLE_MAGIC	"GEOM::ZONEDTBL"
15 /* Appended to the backing provider's name to name the zoned provider. */
16 #define	G_ZONED_SUFFIX		".zoned"
17 
18 /*
19  * Version history:
20  * 1 - Initial version number.
21  */
22 #define	G_ZONED_VERSION		1
23 
24 /*
25  * Sentinel for "unlimited open/active sequential zones".
26  * See SVPD_ZBDC_MAX_SEQ_UNLIMITED.
27  */
28 #define	G_ZONED_SEQ_UNLIMITED	0xffffffff
29 
30 /*
31  * On-disk layout, carved out of the end of the backing provider:
32  *
33  *   [ ... usable (zoned) region ... | table header | zone entries | metadata ]
34  *                                     (1 sector)     (N entries)   (last sec)
35  *
36  * Table header + zone entries hold the live per-zone state and are maintained
37  * entirely by the kernel.
38  */
39 /* On-disk zone-state table header, at the start of the table's first sector. */
40 struct g_zoned_table_hdr {
41 	char		th_magic[16];	/* G_ZONED_TABLE_MAGIC. */
42 	uint32_t	th_version;	/* Version number. */
43 	uint32_t	th_nzones;	/* Zone entries that follow. */
44 };
45 _Static_assert(sizeof(struct g_zoned_table_hdr) == 24,
46     "on-disk zone table header layout changed");
47 
48 /* On-disk zone-state table entry. */
49 struct g_zoned_disk_entry {
50 	uint8_t		de_type;		/* Zone type. */
51 	uint8_t		de_condition;		/* Zone condition. */
52 	uint8_t		de_flags;		/* Zone attribute flags. */
53 	uint8_t		de_reserved;
54 	uint32_t	de_write_pointer;	/* WP offset in the zone. */
55 };
56 #define	G_ZONED_ENTRY_SIZE	(sizeof(struct g_zoned_disk_entry))
57 _Static_assert(G_ZONED_ENTRY_SIZE == 8, "on-disk zone entry layout changed");
58 
59 /*
60  * write_pointer_lba for zones that have no valid write pointer.  Report all
61  * ones across the whole 64-bit field, like a ZBC drive might.
62  */
63 #define	G_ZONED_WP_NONE_LBA	0xffffffffffffffff
64 /* On-disk, zone-relative counterpart in de_write_pointer. */
65 #define	G_ZONED_WP_NONE		0xffffffff
66 /*
67  * Zone size cap (in sectors) keeping every valid zone-relative write pointer,
68  * including that of a full zone (== zone length), below G_ZONED_WP_NONE.
69  */
70 #define	G_ZONED_MAXZONESECS	(G_ZONED_WP_NONE - 1)
71 
72 /*
73  * Maximum number of conventional-zone ranges that fit in the metadata.
74  */
75 #define	G_ZONED_MAXCONV		16
76 
77 /* Maximum number of zones for a single device. */
78 #define	G_ZONED_MAXZONES	UINT32_MAX
79 
80 /*
81  * A run of consecutive conventional (non-sequential-write) zones.  ZBC/ZAC
82  * identify zones by their start LBA, a 64-bit field; zone indices get the same
83  * width.
84  */
85 struct g_zoned_convrange {
86 	uint64_t	cr_first;	/* First zone of the range. */
87 	uint64_t	cr_count;	/* Number of zones in the range. */
88 };
89 
90 /* md_flags bits. */
91 #define	G_ZONED_MD_RESTRICTED_READS	0x00000001 /* URSWRZ off. */
92 #define	G_ZONED_MD_FLAGSMASK		G_ZONED_MD_RESTRICTED_READS
93 
94 struct g_zoned_metadata {
95 	char		md_magic[16];	/* Magic value. */
96 	uint32_t	md_version;	/* Version number. */
97 	uint32_t	md_id;		/* Unique ID. */
98 	uint64_t	md_zonesize;	/* Zone size in bytes. */
99 	uint32_t	md_sectorsize;	/* Provider sector size in bytes. */
100 	uint32_t	md_nconv;	/* Conventional ranges in use. */
101 	uint64_t	md_provsize;	/* Provider size in bytes. */
102 	struct g_zoned_convrange md_conv[G_ZONED_MAXCONV];
103 	uint32_t	md_flags;	/* G_ZONED_MD_* flags. */
104 	uint32_t	md_maxopen;	/* Open zone limit, 0 = unlimited. */
105 };
106 _Static_assert(sizeof(struct g_zoned_metadata) == 312,
107     "on-disk metadata layout changed");
108 _Static_assert(__offsetof(struct g_zoned_metadata, md_conv) == 48,
109     "on-disk metadata layout changed");
110 
111 /*
112  * The on-disk image has the same layout as the struct, so marshalling is a
113  * matter of byte order.  The image is copied in and out rather than cast to,
114  * as the buffers holding it are only guaranteed to be byte aligned.
115  */
116 static __inline void
zoned_metadata_encode(const struct g_zoned_metadata * md,u_char * data)117 zoned_metadata_encode(const struct g_zoned_metadata *md, u_char *data)
118 {
119 	struct g_zoned_metadata d;
120 	u_int i;
121 
122 	memset(&d, 0, sizeof(d));
123 	memcpy(d.md_magic, md->md_magic, sizeof(d.md_magic));
124 	d.md_version = htole32(md->md_version);
125 	d.md_id = htole32(md->md_id);
126 	d.md_zonesize = htole64(md->md_zonesize);
127 	d.md_sectorsize = htole32(md->md_sectorsize);
128 	d.md_nconv = htole32(md->md_nconv);
129 	d.md_provsize = htole64(md->md_provsize);
130 	for (i = 0; i < G_ZONED_MAXCONV; i++) {
131 		d.md_conv[i].cr_first = htole64(md->md_conv[i].cr_first);
132 		d.md_conv[i].cr_count = htole64(md->md_conv[i].cr_count);
133 	}
134 	d.md_flags = htole32(md->md_flags);
135 	d.md_maxopen = htole32(md->md_maxopen);
136 	memcpy(data, &d, sizeof(d));
137 }
138 
139 static __inline void
zoned_metadata_decode(const u_char * data,struct g_zoned_metadata * md)140 zoned_metadata_decode(const u_char *data, struct g_zoned_metadata *md)
141 {
142 	struct g_zoned_metadata d;
143 	u_int i;
144 
145 	memcpy(&d, data, sizeof(d));
146 	memcpy(md->md_magic, d.md_magic, sizeof(md->md_magic));
147 	md->md_version = le32toh(d.md_version);
148 	md->md_id = le32toh(d.md_id);
149 	md->md_zonesize = le64toh(d.md_zonesize);
150 	md->md_sectorsize = le32toh(d.md_sectorsize);
151 	md->md_nconv = le32toh(d.md_nconv);
152 	md->md_provsize = le64toh(d.md_provsize);
153 	for (i = 0; i < G_ZONED_MAXCONV; i++) {
154 		md->md_conv[i].cr_first = le64toh(d.md_conv[i].cr_first);
155 		md->md_conv[i].cr_count = le64toh(d.md_conv[i].cr_count);
156 	}
157 	md->md_flags = le32toh(d.md_flags);
158 	md->md_maxopen = le32toh(d.md_maxopen);
159 }
160 
161 /*
162  * Number of zones a provider of a given geometry can hold after reserving room
163  * at the tail for the metadata sector and the zone-state table.  Returns 0 if
164  * the zone size leaves room for no zone at all, or if it yields more zones
165  * than G_ZONED_MAXZONES.
166  */
167 static __inline uint32_t
g_zoned_nzones(off_t mediasize,off_t zonesize,u_int secsize)168 g_zoned_nzones(off_t mediasize, off_t zonesize, u_int secsize)
169 {
170 	uint64_t nmax, nzones, reserve;
171 
172 	if (zonesize <= 0 || secsize == 0 || mediasize <= 0)
173 		return (0);
174 	/* Over-reserve using the zone count that ignores the reservation. */
175 	nmax = (uint64_t)mediasize / (uint64_t)zonesize;
176 	if (nmax == 0)
177 		return (0);
178 	/* Metadata sector + table header sector + sector-aligned entries. */
179 	reserve = 2 * (uint64_t)secsize +
180 	    roundup2(nmax * G_ZONED_ENTRY_SIZE, (uint64_t)secsize);
181 	if ((uint64_t)mediasize <= reserve)
182 		return (0);
183 	nzones = ((uint64_t)mediasize - reserve) / (uint64_t)zonesize;
184 	/*
185 	 * Reject rather than truncate, as wrapped counts can quietly describe
186 	 * devices of the wrong size.  Note that nzones must be wider than the
187 	 * bound for this to work.
188 	 */
189 	if (nzones > G_ZONED_MAXZONES)
190 		return (0);
191 	return ((uint32_t)nzones);
192 }
193 
194 #ifdef _KERNEL
195 #define	G_ZONED_DEBUG(lvl, ...) \
196     _GEOM_DEBUG("GEOM_ZONED", g_zoned_debug, (lvl), NULL, __VA_ARGS__)
197 #define	G_ZONED_LOGREQLVL(lvl, bp, ...) \
198     _GEOM_DEBUG("GEOM_ZONED", g_zoned_debug, (lvl), (bp), __VA_ARGS__)
199 #define	G_ZONED_LOGREQ(bp, ...)	G_ZONED_LOGREQLVL(2, bp, __VA_ARGS__)
200 
201 /*
202  * Live state of one emulated zoned device. The sc_zones array mirrors the
203  * on-disk zone-state table; dirty entries are re-encoded and written out
204  * lazily on BIO_FLUSH. Zone states are kept in RAM.
205  */
206 struct g_zoned_softc {
207 	struct mtx			 sc_lock;
208 	uint32_t			 sc_id;		/* Unique ID. */
209 	off_t				 sc_zonesize;	/* Zone bytes. */
210 	u_int				 sc_secsize;	/* Sector bytes. */
211 	uint64_t			 sc_zonesecs;	/* Zone sectors. */
212 	uint32_t			 sc_nzones;	/* Number of zones. */
213 	uint64_t			 sc_maxlba;	/* Last LBA + 1. */
214 	uint32_t			 sc_nconv;	/* Conv. ranges. */
215 	uint32_t			 sc_convzones;	/* Conv. zones. */
216 	uint32_t			 sc_maxopen;	/* 0 = unlimited. */
217 	uint32_t			 sc_nopen;	/* Open zones. */
218 	bool				 sc_rdrestrict;	/* URSWRZ off. */
219 	struct g_zoned_convrange	 sc_conv[G_ZONED_MAXCONV];
220 	struct disk_zone_rep_entry	*sc_zones;	/* sc_nzones long. */
221 	/* Zone-state table placement and dirty tracking. */
222 	uint32_t			 sc_tabsecs;	/* Table sectors. */
223 	off_t				 sc_taboff;	/* Table offset. */
224 	bool				 sc_dirty;	/* Pending writes. */
225 	bool				 sc_hdrdirty;	/* Header dirty. */
226 	uint32_t			 sc_dirtylo;	/* First dirty zone. */
227 	uint32_t			 sc_dirtyhi;	/* Last dirty zone. */
228 	/* Statistics. */
229 	uintmax_t			 sc_reads;
230 	uintmax_t			 sc_writes;
231 	uintmax_t			 sc_readbytes;
232 	uintmax_t			 sc_wrotebytes;
233 	uintmax_t			 sc_zonecmds;
234 };
235 
236 static __inline void
zoned_table_hdr_encode(const struct g_zoned_table_hdr * th,u_char * data)237 zoned_table_hdr_encode(const struct g_zoned_table_hdr *th, u_char *data)
238 {
239 	struct g_zoned_table_hdr d;
240 
241 	memset(&d, 0, sizeof(d));
242 	memcpy(d.th_magic, th->th_magic, sizeof(d.th_magic));
243 	d.th_version = htole32(th->th_version);
244 	d.th_nzones = htole32(th->th_nzones);
245 	memcpy(data, &d, sizeof(d));
246 }
247 
248 static __inline void
zoned_table_hdr_decode(const u_char * data,struct g_zoned_table_hdr * th)249 zoned_table_hdr_decode(const u_char *data, struct g_zoned_table_hdr *th)
250 {
251 	struct g_zoned_table_hdr d;
252 
253 	memcpy(&d, data, sizeof(d));
254 	memcpy(th->th_magic, d.th_magic, sizeof(th->th_magic));
255 	th->th_version = le32toh(d.th_version);
256 	th->th_nzones = le32toh(d.th_nzones);
257 }
258 
259 static __inline void
g_zoned_entry_encode(const struct disk_zone_rep_entry * z,u_char * data)260 g_zoned_entry_encode(const struct disk_zone_rep_entry *z, u_char *data)
261 {
262 	struct g_zoned_disk_entry de;
263 
264 	de.de_type = z->zone_type;
265 	de.de_condition = z->zone_condition;
266 	de.de_flags = z->zone_flags;
267 	de.de_reserved = 0;
268 	de.de_write_pointer = htole32(
269 	    z->write_pointer_lba == G_ZONED_WP_NONE_LBA ? G_ZONED_WP_NONE :
270 	    (uint32_t)(z->write_pointer_lba - z->zone_start_lba));
271 	memcpy(data, &de, sizeof(de));
272 }
273 
274 static __inline void
g_zoned_entry_decode(const u_char * data,struct disk_zone_rep_entry * z,uint64_t start_lba)275 g_zoned_entry_decode(const u_char *data, struct disk_zone_rep_entry *z,
276     uint64_t start_lba)
277 {
278 	struct g_zoned_disk_entry de;
279 	uint32_t wp;
280 
281 	memcpy(&de, data, sizeof(de));
282 	z->zone_type = de.de_type;
283 	z->zone_condition = de.de_condition;
284 	z->zone_flags = de.de_flags;
285 	wp = le32toh(de.de_write_pointer);
286 	z->write_pointer_lba = (wp == G_ZONED_WP_NONE) ? G_ZONED_WP_NONE_LBA :
287 	    start_lba + wp;
288 }
289 #endif	/* _KERNEL */
290 
291 #endif	/* _G_ZONED_H_ */
292