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