1 1.20 christos /* $NetBSD: rf_paritylog.c,v 1.20 2019/10/10 03:43:59 christos Exp $ */ 2 1.1 oster /* 3 1.1 oster * Copyright (c) 1995 Carnegie-Mellon University. 4 1.1 oster * All rights reserved. 5 1.1 oster * 6 1.1 oster * Author: William V. Courtright II 7 1.1 oster * 8 1.1 oster * Permission to use, copy, modify and distribute this software and 9 1.1 oster * its documentation is hereby granted, provided that both the copyright 10 1.1 oster * notice and this permission notice appear in all copies of the 11 1.1 oster * software, derivative works or modified versions, and any portions 12 1.1 oster * thereof, and that both notices appear in supporting documentation. 13 1.1 oster * 14 1.1 oster * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" 15 1.1 oster * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND 16 1.1 oster * FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. 17 1.1 oster * 18 1.1 oster * Carnegie Mellon requests users of this software to return to 19 1.1 oster * 20 1.1 oster * Software Distribution Coordinator or Software.Distribution (at) CS.CMU.EDU 21 1.1 oster * School of Computer Science 22 1.1 oster * Carnegie Mellon University 23 1.1 oster * Pittsburgh PA 15213-3890 24 1.1 oster * 25 1.1 oster * any improvements or extensions that they make and grant Carnegie the 26 1.1 oster * rights to redistribute these changes. 27 1.1 oster */ 28 1.1 oster 29 1.1 oster /* Code for manipulating in-core parity logs 30 1.1 oster * 31 1.1 oster */ 32 1.7 lukem 33 1.7 lukem #include <sys/cdefs.h> 34 1.20 christos __KERNEL_RCSID(0, "$NetBSD: rf_paritylog.c,v 1.20 2019/10/10 03:43:59 christos Exp $"); 35 1.1 oster 36 1.1 oster #include "rf_archs.h" 37 1.1 oster 38 1.1 oster #if RF_INCLUDE_PARITYLOGGING > 0 39 1.1 oster 40 1.1 oster /* 41 1.1 oster * Append-only log for recording parity "update" and "overwrite" records 42 1.1 oster */ 43 1.1 oster 44 1.6 oster #include <dev/raidframe/raidframevar.h> 45 1.6 oster 46 1.1 oster #include "rf_threadstuff.h" 47 1.1 oster #include "rf_mcpair.h" 48 1.1 oster #include "rf_raid.h" 49 1.1 oster #include "rf_dag.h" 50 1.1 oster #include "rf_dagfuncs.h" 51 1.1 oster #include "rf_desc.h" 52 1.1 oster #include "rf_layout.h" 53 1.1 oster #include "rf_diskqueue.h" 54 1.1 oster #include "rf_etimer.h" 55 1.1 oster #include "rf_paritylog.h" 56 1.1 oster #include "rf_general.h" 57 1.1 oster #include "rf_map.h" 58 1.1 oster #include "rf_paritylogging.h" 59 1.1 oster #include "rf_paritylogDiskMgr.h" 60 1.1 oster 61 1.3 oster static RF_CommonLogData_t * 62 1.3 oster AllocParityLogCommonData(RF_Raid_t * raidPtr) 63 1.1 oster { 64 1.3 oster RF_CommonLogData_t *common = NULL; 65 1.1 oster 66 1.3 oster /* Return a struct for holding common parity log information from the 67 1.3 oster * free list (rf_parityLogDiskQueue.freeCommonList). If the free list 68 1.3 oster * is empty, call RF_Malloc to create a new structure. NON-BLOCKING */ 69 1.3 oster 70 1.15 mrg rf_lock_mutex2(raidPtr->parityLogDiskQueue.mutex); 71 1.3 oster if (raidPtr->parityLogDiskQueue.freeCommonList) { 72 1.3 oster common = raidPtr->parityLogDiskQueue.freeCommonList; 73 1.3 oster raidPtr->parityLogDiskQueue.freeCommonList = raidPtr->parityLogDiskQueue.freeCommonList->next; 74 1.15 mrg rf_unlock_mutex2(raidPtr->parityLogDiskQueue.mutex); 75 1.3 oster } else { 76 1.15 mrg rf_unlock_mutex2(raidPtr->parityLogDiskQueue.mutex); 77 1.19 christos common = RF_Malloc(sizeof(*common)); 78 1.14 mrg /* destroy is in rf_paritylogging.c */ 79 1.14 mrg rf_init_mutex2(common->mutex, IPL_VM); 80 1.3 oster } 81 1.3 oster common->next = NULL; 82 1.3 oster return (common); 83 1.3 oster } 84 1.3 oster 85 1.10 perry static void 86 1.3 oster FreeParityLogCommonData(RF_CommonLogData_t * common) 87 1.3 oster { 88 1.3 oster RF_Raid_t *raidPtr; 89 1.3 oster 90 1.3 oster /* Insert a single struct for holding parity log information (data) 91 1.3 oster * into the free list (rf_parityLogDiskQueue.freeCommonList). 92 1.3 oster * NON-BLOCKING */ 93 1.3 oster 94 1.3 oster raidPtr = common->raidPtr; 95 1.15 mrg rf_lock_mutex2(raidPtr->parityLogDiskQueue.mutex); 96 1.3 oster common->next = raidPtr->parityLogDiskQueue.freeCommonList; 97 1.3 oster raidPtr->parityLogDiskQueue.freeCommonList = common; 98 1.15 mrg rf_unlock_mutex2(raidPtr->parityLogDiskQueue.mutex); 99 1.3 oster } 100 1.3 oster 101 1.3 oster static RF_ParityLogData_t * 102 1.3 oster AllocParityLogData(RF_Raid_t * raidPtr) 103 1.3 oster { 104 1.3 oster RF_ParityLogData_t *data = NULL; 105 1.3 oster 106 1.3 oster /* Return a struct for holding parity log information from the free 107 1.3 oster * list (rf_parityLogDiskQueue.freeList). If the free list is empty, 108 1.3 oster * call RF_Malloc to create a new structure. NON-BLOCKING */ 109 1.3 oster 110 1.15 mrg rf_lock_mutex2(raidPtr->parityLogDiskQueue.mutex); 111 1.3 oster if (raidPtr->parityLogDiskQueue.freeDataList) { 112 1.3 oster data = raidPtr->parityLogDiskQueue.freeDataList; 113 1.3 oster raidPtr->parityLogDiskQueue.freeDataList = raidPtr->parityLogDiskQueue.freeDataList->next; 114 1.15 mrg rf_unlock_mutex2(raidPtr->parityLogDiskQueue.mutex); 115 1.3 oster } else { 116 1.15 mrg rf_unlock_mutex2(raidPtr->parityLogDiskQueue.mutex); 117 1.19 christos data = RF_Malloc(sizeof(*data)); 118 1.3 oster } 119 1.3 oster data->next = NULL; 120 1.3 oster data->prev = NULL; 121 1.3 oster return (data); 122 1.3 oster } 123 1.3 oster 124 1.3 oster 125 1.10 perry static void 126 1.3 oster FreeParityLogData(RF_ParityLogData_t * data) 127 1.3 oster { 128 1.3 oster RF_ParityLogData_t *nextItem; 129 1.3 oster RF_Raid_t *raidPtr; 130 1.3 oster 131 1.3 oster /* Insert a linked list of structs for holding parity log information 132 1.3 oster * (data) into the free list (parityLogDiskQueue.freeList). 133 1.3 oster * NON-BLOCKING */ 134 1.3 oster 135 1.3 oster raidPtr = data->common->raidPtr; 136 1.15 mrg rf_lock_mutex2(raidPtr->parityLogDiskQueue.mutex); 137 1.3 oster while (data) { 138 1.3 oster nextItem = data->next; 139 1.3 oster data->next = raidPtr->parityLogDiskQueue.freeDataList; 140 1.3 oster raidPtr->parityLogDiskQueue.freeDataList = data; 141 1.3 oster data = nextItem; 142 1.3 oster } 143 1.15 mrg rf_unlock_mutex2(raidPtr->parityLogDiskQueue.mutex); 144 1.3 oster } 145 1.3 oster 146 1.3 oster 147 1.10 perry static void 148 1.3 oster EnqueueParityLogData( 149 1.3 oster RF_ParityLogData_t * data, 150 1.3 oster RF_ParityLogData_t ** head, 151 1.3 oster RF_ParityLogData_t ** tail) 152 1.3 oster { 153 1.3 oster RF_Raid_t *raidPtr; 154 1.3 oster 155 1.3 oster /* Insert an in-core parity log (*data) into the head of a disk queue 156 1.3 oster * (*head, *tail). NON-BLOCKING */ 157 1.3 oster 158 1.3 oster raidPtr = data->common->raidPtr; 159 1.3 oster if (rf_parityLogDebug) 160 1.3 oster printf("[enqueueing parity log data, region %d, raidAddress %d, numSector %d]\n", data->regionID, (int) data->diskAddress.raidAddress, (int) data->diskAddress.numSector); 161 1.3 oster RF_ASSERT(data->prev == NULL); 162 1.3 oster RF_ASSERT(data->next == NULL); 163 1.15 mrg rf_lock_mutex2(raidPtr->parityLogDiskQueue.mutex); 164 1.3 oster if (*head) { 165 1.3 oster /* insert into head of queue */ 166 1.3 oster RF_ASSERT((*head)->prev == NULL); 167 1.3 oster RF_ASSERT((*tail)->next == NULL); 168 1.3 oster data->next = *head; 169 1.3 oster (*head)->prev = data; 170 1.3 oster *head = data; 171 1.3 oster } else { 172 1.3 oster /* insert into empty list */ 173 1.3 oster RF_ASSERT(*head == NULL); 174 1.3 oster RF_ASSERT(*tail == NULL); 175 1.3 oster *head = data; 176 1.3 oster *tail = data; 177 1.3 oster } 178 1.3 oster RF_ASSERT((*head)->prev == NULL); 179 1.3 oster RF_ASSERT((*tail)->next == NULL); 180 1.15 mrg rf_unlock_mutex2(raidPtr->parityLogDiskQueue.mutex); 181 1.3 oster } 182 1.3 oster 183 1.3 oster static RF_ParityLogData_t * 184 1.3 oster DequeueParityLogData( 185 1.3 oster RF_Raid_t * raidPtr, 186 1.3 oster RF_ParityLogData_t ** head, 187 1.3 oster RF_ParityLogData_t ** tail, 188 1.3 oster int ignoreLocks) 189 1.3 oster { 190 1.3 oster RF_ParityLogData_t *data; 191 1.3 oster 192 1.3 oster /* Remove and return an in-core parity log from the tail of a disk 193 1.3 oster * queue (*head, *tail). NON-BLOCKING */ 194 1.3 oster 195 1.3 oster /* remove from tail, preserving FIFO order */ 196 1.3 oster if (!ignoreLocks) 197 1.15 mrg rf_lock_mutex2(raidPtr->parityLogDiskQueue.mutex); 198 1.3 oster data = *tail; 199 1.3 oster if (data) { 200 1.3 oster if (*head == *tail) { 201 1.3 oster /* removing last item from queue */ 202 1.3 oster *head = NULL; 203 1.3 oster *tail = NULL; 204 1.3 oster } else { 205 1.3 oster *tail = (*tail)->prev; 206 1.3 oster (*tail)->next = NULL; 207 1.3 oster RF_ASSERT((*head)->prev == NULL); 208 1.3 oster RF_ASSERT((*tail)->next == NULL); 209 1.3 oster } 210 1.3 oster data->next = NULL; 211 1.3 oster data->prev = NULL; 212 1.3 oster if (rf_parityLogDebug) 213 1.3 oster printf("[dequeueing parity log data, region %d, raidAddress %d, numSector %d]\n", data->regionID, (int) data->diskAddress.raidAddress, (int) data->diskAddress.numSector); 214 1.3 oster } 215 1.3 oster if (*head) { 216 1.3 oster RF_ASSERT((*head)->prev == NULL); 217 1.3 oster RF_ASSERT((*tail)->next == NULL); 218 1.3 oster } 219 1.3 oster if (!ignoreLocks) 220 1.15 mrg rf_unlock_mutex2(raidPtr->parityLogDiskQueue.mutex); 221 1.3 oster return (data); 222 1.3 oster } 223 1.3 oster 224 1.3 oster 225 1.10 perry static void 226 1.3 oster RequeueParityLogData( 227 1.3 oster RF_ParityLogData_t * data, 228 1.3 oster RF_ParityLogData_t ** head, 229 1.3 oster RF_ParityLogData_t ** tail) 230 1.3 oster { 231 1.3 oster RF_Raid_t *raidPtr; 232 1.3 oster 233 1.3 oster /* Insert an in-core parity log (*data) into the tail of a disk queue 234 1.3 oster * (*head, *tail). NON-BLOCKING */ 235 1.3 oster 236 1.3 oster raidPtr = data->common->raidPtr; 237 1.3 oster RF_ASSERT(data); 238 1.3 oster if (rf_parityLogDebug) 239 1.3 oster printf("[requeueing parity log data, region %d, raidAddress %d, numSector %d]\n", data->regionID, (int) data->diskAddress.raidAddress, (int) data->diskAddress.numSector); 240 1.15 mrg rf_lock_mutex2(raidPtr->parityLogDiskQueue.mutex); 241 1.3 oster if (*tail) { 242 1.3 oster /* append to tail of list */ 243 1.3 oster data->prev = *tail; 244 1.3 oster data->next = NULL; 245 1.3 oster (*tail)->next = data; 246 1.3 oster *tail = data; 247 1.3 oster } else { 248 1.3 oster /* inserting into an empty list */ 249 1.3 oster *head = data; 250 1.3 oster *tail = data; 251 1.3 oster (*head)->prev = NULL; 252 1.3 oster (*tail)->next = NULL; 253 1.3 oster } 254 1.3 oster RF_ASSERT((*head)->prev == NULL); 255 1.3 oster RF_ASSERT((*tail)->next == NULL); 256 1.15 mrg rf_unlock_mutex2(raidPtr->parityLogDiskQueue.mutex); 257 1.3 oster } 258 1.3 oster 259 1.3 oster RF_ParityLogData_t * 260 1.3 oster rf_CreateParityLogData( 261 1.3 oster RF_ParityRecordType_t operation, 262 1.3 oster RF_PhysDiskAddr_t * pda, 263 1.13 christos void *bufPtr, 264 1.3 oster RF_Raid_t * raidPtr, 265 1.20 christos void (*wakeFunc)(void *, int), 266 1.3 oster void *wakeArg, 267 1.3 oster RF_AccTraceEntry_t * tracerec, 268 1.3 oster RF_Etimer_t startTime) 269 1.3 oster { 270 1.3 oster RF_ParityLogData_t *data, *resultHead = NULL, *resultTail = NULL; 271 1.3 oster RF_CommonLogData_t *common; 272 1.3 oster RF_PhysDiskAddr_t *diskAddress; 273 1.3 oster int boundary, offset = 0; 274 1.3 oster 275 1.3 oster /* Return an initialized struct of info to be logged. Build one item 276 1.3 oster * per physical disk address, one item per region. 277 1.10 perry * 278 1.3 oster * NON-BLOCKING */ 279 1.3 oster 280 1.3 oster diskAddress = pda; 281 1.3 oster common = AllocParityLogCommonData(raidPtr); 282 1.3 oster RF_ASSERT(common); 283 1.3 oster 284 1.3 oster common->operation = operation; 285 1.3 oster common->bufPtr = bufPtr; 286 1.3 oster common->raidPtr = raidPtr; 287 1.3 oster common->wakeFunc = wakeFunc; 288 1.3 oster common->wakeArg = wakeArg; 289 1.3 oster common->tracerec = tracerec; 290 1.3 oster common->startTime = startTime; 291 1.3 oster common->cnt = 0; 292 1.3 oster 293 1.3 oster if (rf_parityLogDebug) 294 1.3 oster printf("[entering CreateParityLogData]\n"); 295 1.3 oster while (diskAddress) { 296 1.3 oster common->cnt++; 297 1.3 oster data = AllocParityLogData(raidPtr); 298 1.3 oster RF_ASSERT(data); 299 1.3 oster data->common = common; 300 1.3 oster data->next = NULL; 301 1.3 oster data->prev = NULL; 302 1.3 oster data->regionID = rf_MapRegionIDParityLogging(raidPtr, diskAddress->startSector); 303 1.3 oster if (data->regionID == rf_MapRegionIDParityLogging(raidPtr, diskAddress->startSector + diskAddress->numSector - 1)) { 304 1.3 oster /* disk address does not cross a region boundary */ 305 1.3 oster data->diskAddress = *diskAddress; 306 1.3 oster data->bufOffset = offset; 307 1.3 oster offset = offset + diskAddress->numSector; 308 1.3 oster EnqueueParityLogData(data, &resultHead, &resultTail); 309 1.3 oster /* adjust disk address */ 310 1.3 oster diskAddress = diskAddress->next; 311 1.3 oster } else { 312 1.3 oster /* disk address crosses a region boundary */ 313 1.3 oster /* find address where region is crossed */ 314 1.3 oster boundary = 0; 315 1.3 oster while (data->regionID == rf_MapRegionIDParityLogging(raidPtr, diskAddress->startSector + boundary)) 316 1.3 oster boundary++; 317 1.3 oster 318 1.3 oster /* enter data before the boundary */ 319 1.3 oster data->diskAddress = *diskAddress; 320 1.3 oster data->diskAddress.numSector = boundary; 321 1.3 oster data->bufOffset = offset; 322 1.3 oster offset += boundary; 323 1.3 oster EnqueueParityLogData(data, &resultHead, &resultTail); 324 1.3 oster /* adjust disk address */ 325 1.3 oster diskAddress->startSector += boundary; 326 1.3 oster diskAddress->numSector -= boundary; 327 1.3 oster } 328 1.1 oster } 329 1.3 oster if (rf_parityLogDebug) 330 1.3 oster printf("[leaving CreateParityLogData]\n"); 331 1.3 oster return (resultHead); 332 1.3 oster } 333 1.3 oster 334 1.3 oster 335 1.3 oster RF_ParityLogData_t * 336 1.3 oster rf_SearchAndDequeueParityLogData( 337 1.3 oster RF_Raid_t * raidPtr, 338 1.3 oster int regionID, 339 1.3 oster RF_ParityLogData_t ** head, 340 1.3 oster RF_ParityLogData_t ** tail, 341 1.3 oster int ignoreLocks) 342 1.3 oster { 343 1.3 oster RF_ParityLogData_t *w; 344 1.3 oster 345 1.3 oster /* Remove and return an in-core parity log from a specified region 346 1.3 oster * (regionID). If a matching log is not found, return NULL. 347 1.10 perry * 348 1.3 oster * NON-BLOCKING. */ 349 1.3 oster 350 1.3 oster /* walk backward through a list, looking for an entry with a matching 351 1.3 oster * region ID */ 352 1.3 oster if (!ignoreLocks) 353 1.15 mrg rf_lock_mutex2(raidPtr->parityLogDiskQueue.mutex); 354 1.3 oster w = (*tail); 355 1.3 oster while (w) { 356 1.3 oster if (w->regionID == regionID) { 357 1.3 oster /* remove an element from the list */ 358 1.3 oster if (w == *tail) { 359 1.3 oster if (*head == *tail) { 360 1.3 oster /* removing only element in the list */ 361 1.3 oster *head = NULL; 362 1.3 oster *tail = NULL; 363 1.3 oster } else { 364 1.3 oster /* removing last item in the list */ 365 1.3 oster *tail = (*tail)->prev; 366 1.3 oster (*tail)->next = NULL; 367 1.3 oster RF_ASSERT((*head)->prev == NULL); 368 1.3 oster RF_ASSERT((*tail)->next == NULL); 369 1.3 oster } 370 1.3 oster } else { 371 1.3 oster if (w == *head) { 372 1.3 oster /* removing first item in the list */ 373 1.3 oster *head = (*head)->next; 374 1.3 oster (*head)->prev = NULL; 375 1.3 oster RF_ASSERT((*head)->prev == NULL); 376 1.3 oster RF_ASSERT((*tail)->next == NULL); 377 1.3 oster } else { 378 1.3 oster /* removing an item from the middle of 379 1.3 oster * the list */ 380 1.3 oster w->prev->next = w->next; 381 1.3 oster w->next->prev = w->prev; 382 1.3 oster RF_ASSERT((*head)->prev == NULL); 383 1.3 oster RF_ASSERT((*tail)->next == NULL); 384 1.3 oster } 385 1.3 oster } 386 1.3 oster w->prev = NULL; 387 1.3 oster w->next = NULL; 388 1.3 oster if (rf_parityLogDebug) 389 1.3 oster printf("[dequeueing parity log data, region %d, raidAddress %d, numSector %d]\n", w->regionID, (int) w->diskAddress.raidAddress, (int) w->diskAddress.numSector); 390 1.3 oster return (w); 391 1.3 oster } else 392 1.3 oster w = w->prev; 393 1.3 oster } 394 1.3 oster if (!ignoreLocks) 395 1.15 mrg rf_unlock_mutex2(raidPtr->parityLogDiskQueue.mutex); 396 1.3 oster return (NULL); 397 1.3 oster } 398 1.3 oster 399 1.3 oster static RF_ParityLogData_t * 400 1.3 oster DequeueMatchingLogData( 401 1.3 oster RF_Raid_t * raidPtr, 402 1.3 oster RF_ParityLogData_t ** head, 403 1.3 oster RF_ParityLogData_t ** tail) 404 1.3 oster { 405 1.3 oster RF_ParityLogData_t *logDataList, *logData; 406 1.3 oster int regionID; 407 1.3 oster 408 1.3 oster /* Remove and return an in-core parity log from the tail of a disk 409 1.3 oster * queue (*head, *tail). Then remove all matching (identical 410 1.3 oster * regionIDs) logData and return as a linked list. 411 1.10 perry * 412 1.3 oster * NON-BLOCKING */ 413 1.3 oster 414 1.3 oster logDataList = DequeueParityLogData(raidPtr, head, tail, RF_TRUE); 415 1.3 oster if (logDataList) { 416 1.3 oster regionID = logDataList->regionID; 417 1.3 oster logData = logDataList; 418 1.3 oster logData->next = rf_SearchAndDequeueParityLogData(raidPtr, regionID, head, tail, RF_TRUE); 419 1.3 oster while (logData->next) { 420 1.3 oster logData = logData->next; 421 1.3 oster logData->next = rf_SearchAndDequeueParityLogData(raidPtr, regionID, head, tail, RF_TRUE); 422 1.3 oster } 423 1.1 oster } 424 1.3 oster return (logDataList); 425 1.1 oster } 426 1.1 oster 427 1.1 oster 428 1.3 oster static RF_ParityLog_t * 429 1.3 oster AcquireParityLog( 430 1.3 oster RF_ParityLogData_t * logData, 431 1.3 oster int finish) 432 1.3 oster { 433 1.3 oster RF_ParityLog_t *log = NULL; 434 1.3 oster RF_Raid_t *raidPtr; 435 1.3 oster 436 1.3 oster /* Grab a log buffer from the pool and return it. If no buffers are 437 1.3 oster * available, return NULL. NON-BLOCKING */ 438 1.3 oster raidPtr = logData->common->raidPtr; 439 1.18 mrg rf_lock_mutex2(raidPtr->parityLogPool.mutex); 440 1.3 oster if (raidPtr->parityLogPool.parityLogs) { 441 1.3 oster log = raidPtr->parityLogPool.parityLogs; 442 1.3 oster raidPtr->parityLogPool.parityLogs = raidPtr->parityLogPool.parityLogs->next; 443 1.3 oster log->regionID = logData->regionID; 444 1.3 oster log->numRecords = 0; 445 1.3 oster log->next = NULL; 446 1.3 oster raidPtr->logsInUse++; 447 1.3 oster RF_ASSERT(raidPtr->logsInUse >= 0 && raidPtr->logsInUse <= raidPtr->numParityLogs); 448 1.3 oster } else { 449 1.3 oster /* no logs available, so place ourselves on the queue of work 450 1.3 oster * waiting on log buffers this is done while 451 1.3 oster * parityLogPool.mutex is held, to ensure synchronization with 452 1.3 oster * ReleaseParityLogs. */ 453 1.3 oster if (rf_parityLogDebug) 454 1.3 oster printf("[blocked on log, region %d, finish %d]\n", logData->regionID, finish); 455 1.3 oster if (finish) 456 1.3 oster RequeueParityLogData(logData, &raidPtr->parityLogDiskQueue.logBlockHead, &raidPtr->parityLogDiskQueue.logBlockTail); 457 1.3 oster else 458 1.3 oster EnqueueParityLogData(logData, &raidPtr->parityLogDiskQueue.logBlockHead, &raidPtr->parityLogDiskQueue.logBlockTail); 459 1.1 oster } 460 1.18 mrg rf_unlock_mutex2(raidPtr->parityLogPool.mutex); 461 1.3 oster return (log); 462 1.1 oster } 463 1.1 oster 464 1.10 perry void 465 1.3 oster rf_ReleaseParityLogs( 466 1.3 oster RF_Raid_t * raidPtr, 467 1.3 oster RF_ParityLog_t * firstLog) 468 1.3 oster { 469 1.3 oster RF_ParityLogData_t *logDataList; 470 1.3 oster RF_ParityLog_t *log, *lastLog; 471 1.3 oster int cnt; 472 1.1 oster 473 1.3 oster /* Insert a linked list of parity logs (firstLog) to the free list 474 1.3 oster * (parityLogPool.parityLogPool) 475 1.10 perry * 476 1.3 oster * NON-BLOCKING. */ 477 1.3 oster 478 1.3 oster RF_ASSERT(firstLog); 479 1.3 oster 480 1.3 oster /* Before returning logs to global free list, service all requests 481 1.3 oster * which are blocked on logs. Holding mutexes for parityLogPool and 482 1.3 oster * parityLogDiskQueue forces synchronization with AcquireParityLog(). */ 483 1.18 mrg rf_lock_mutex2(raidPtr->parityLogPool.mutex); 484 1.15 mrg rf_lock_mutex2(raidPtr->parityLogDiskQueue.mutex); 485 1.1 oster logDataList = DequeueMatchingLogData(raidPtr, &raidPtr->parityLogDiskQueue.logBlockHead, &raidPtr->parityLogDiskQueue.logBlockTail); 486 1.3 oster log = firstLog; 487 1.3 oster if (firstLog) 488 1.3 oster firstLog = firstLog->next; 489 1.3 oster log->numRecords = 0; 490 1.3 oster log->next = NULL; 491 1.3 oster while (logDataList && log) { 492 1.18 mrg rf_unlock_mutex2(raidPtr->parityLogPool.mutex); 493 1.15 mrg rf_unlock_mutex2(raidPtr->parityLogDiskQueue.mutex); 494 1.3 oster rf_ParityLogAppend(logDataList, RF_TRUE, &log, RF_FALSE); 495 1.3 oster if (rf_parityLogDebug) 496 1.3 oster printf("[finishing up buf-blocked log data, region %d]\n", logDataList->regionID); 497 1.3 oster if (log == NULL) { 498 1.3 oster log = firstLog; 499 1.3 oster if (firstLog) { 500 1.3 oster firstLog = firstLog->next; 501 1.3 oster log->numRecords = 0; 502 1.3 oster log->next = NULL; 503 1.3 oster } 504 1.3 oster } 505 1.18 mrg rf_lock_mutex2(raidPtr->parityLogPool.mutex); 506 1.15 mrg rf_lock_mutex2(raidPtr->parityLogDiskQueue.mutex); 507 1.3 oster if (log) 508 1.3 oster logDataList = DequeueMatchingLogData(raidPtr, &raidPtr->parityLogDiskQueue.logBlockHead, &raidPtr->parityLogDiskQueue.logBlockTail); 509 1.3 oster } 510 1.3 oster /* return remaining logs to pool */ 511 1.3 oster if (log) { 512 1.3 oster log->next = firstLog; 513 1.3 oster firstLog = log; 514 1.3 oster } 515 1.3 oster if (firstLog) { 516 1.3 oster lastLog = firstLog; 517 1.3 oster raidPtr->logsInUse--; 518 1.3 oster RF_ASSERT(raidPtr->logsInUse >= 0 && raidPtr->logsInUse <= raidPtr->numParityLogs); 519 1.3 oster while (lastLog->next) { 520 1.3 oster lastLog = lastLog->next; 521 1.3 oster raidPtr->logsInUse--; 522 1.3 oster RF_ASSERT(raidPtr->logsInUse >= 0 && raidPtr->logsInUse <= raidPtr->numParityLogs); 523 1.3 oster } 524 1.3 oster lastLog->next = raidPtr->parityLogPool.parityLogs; 525 1.3 oster raidPtr->parityLogPool.parityLogs = firstLog; 526 1.3 oster cnt = 0; 527 1.3 oster log = raidPtr->parityLogPool.parityLogs; 528 1.3 oster while (log) { 529 1.3 oster cnt++; 530 1.3 oster log = log->next; 531 1.3 oster } 532 1.3 oster RF_ASSERT(cnt + raidPtr->logsInUse == raidPtr->numParityLogs); 533 1.1 oster } 534 1.18 mrg rf_unlock_mutex2(raidPtr->parityLogPool.mutex); 535 1.15 mrg rf_unlock_mutex2(raidPtr->parityLogDiskQueue.mutex); 536 1.3 oster } 537 1.1 oster 538 1.10 perry static void 539 1.3 oster ReintLog( 540 1.3 oster RF_Raid_t * raidPtr, 541 1.3 oster int regionID, 542 1.3 oster RF_ParityLog_t * log) 543 1.3 oster { 544 1.3 oster RF_ASSERT(log); 545 1.3 oster 546 1.3 oster /* Insert an in-core parity log (log) into the disk queue of 547 1.3 oster * reintegration work. Set the flag (reintInProgress) for the 548 1.3 oster * specified region (regionID) to indicate that reintegration is in 549 1.3 oster * progress for this region. NON-BLOCKING */ 550 1.3 oster 551 1.16 mrg rf_lock_mutex2(raidPtr->regionInfo[regionID].reintMutex); 552 1.3 oster raidPtr->regionInfo[regionID].reintInProgress = RF_TRUE; /* cleared when reint 553 1.3 oster * complete */ 554 1.3 oster 555 1.3 oster if (rf_parityLogDebug) 556 1.3 oster printf("[requesting reintegration of region %d]\n", log->regionID); 557 1.3 oster /* move record to reintegration queue */ 558 1.15 mrg rf_lock_mutex2(raidPtr->parityLogDiskQueue.mutex); 559 1.3 oster log->next = raidPtr->parityLogDiskQueue.reintQueue; 560 1.3 oster raidPtr->parityLogDiskQueue.reintQueue = log; 561 1.16 mrg rf_unlock_mutex2(raidPtr->regionInfo[regionID].reintMutex); 562 1.15 mrg rf_signal_cond2(raidPtr->parityLogDiskQueue.cond); 563 1.15 mrg rf_unlock_mutex2(raidPtr->parityLogDiskQueue.mutex); 564 1.3 oster } 565 1.3 oster 566 1.10 perry static void 567 1.3 oster FlushLog( 568 1.3 oster RF_Raid_t * raidPtr, 569 1.3 oster RF_ParityLog_t * log) 570 1.3 oster { 571 1.3 oster /* insert a core log (log) into a list of logs 572 1.3 oster * (parityLogDiskQueue.flushQueue) waiting to be written to disk. 573 1.3 oster * NON-BLOCKING */ 574 1.3 oster 575 1.3 oster RF_ASSERT(log); 576 1.3 oster RF_ASSERT(log->numRecords == raidPtr->numSectorsPerLog); 577 1.3 oster RF_ASSERT(log->next == NULL); 578 1.3 oster /* move log to flush queue */ 579 1.15 mrg rf_lock_mutex2(raidPtr->parityLogDiskQueue.mutex); 580 1.3 oster log->next = raidPtr->parityLogDiskQueue.flushQueue; 581 1.3 oster raidPtr->parityLogDiskQueue.flushQueue = log; 582 1.15 mrg rf_signal_cond2(raidPtr->parityLogDiskQueue.cond); 583 1.15 mrg rf_unlock_mutex2(raidPtr->parityLogDiskQueue.mutex); 584 1.3 oster } 585 1.3 oster 586 1.10 perry static int 587 1.3 oster DumpParityLogToDisk( 588 1.3 oster int finish, 589 1.3 oster RF_ParityLogData_t * logData) 590 1.3 oster { 591 1.3 oster int i, diskCount, regionID = logData->regionID; 592 1.3 oster RF_ParityLog_t *log; 593 1.3 oster RF_Raid_t *raidPtr; 594 1.3 oster 595 1.3 oster raidPtr = logData->common->raidPtr; 596 1.3 oster 597 1.3 oster /* Move a core log to disk. If the log disk is full, initiate 598 1.3 oster * reintegration. 599 1.10 perry * 600 1.3 oster * Return (0) if we can enqueue the dump immediately, otherwise return 601 1.3 oster * (1) to indicate we are blocked on reintegration and control of the 602 1.3 oster * thread should be relinquished. 603 1.10 perry * 604 1.3 oster * Caller must hold regionInfo[regionID].mutex 605 1.10 perry * 606 1.3 oster * NON-BLOCKING */ 607 1.3 oster 608 1.17 mrg RF_ASSERT(rf_owned_mutex2(raidPtr->regionInfo[regionID].mutex)); 609 1.17 mrg 610 1.3 oster if (rf_parityLogDebug) 611 1.3 oster printf("[dumping parity log to disk, region %d]\n", regionID); 612 1.3 oster log = raidPtr->regionInfo[regionID].coreLog; 613 1.3 oster RF_ASSERT(log->numRecords == raidPtr->numSectorsPerLog); 614 1.3 oster RF_ASSERT(log->next == NULL); 615 1.3 oster 616 1.3 oster /* if reintegration is in progress, must queue work */ 617 1.16 mrg rf_lock_mutex2(raidPtr->regionInfo[regionID].reintMutex); 618 1.3 oster if (raidPtr->regionInfo[regionID].reintInProgress) { 619 1.3 oster /* Can not proceed since this region is currently being 620 1.3 oster * reintegrated. We can not block, so queue remaining work and 621 1.3 oster * return */ 622 1.3 oster if (rf_parityLogDebug) 623 1.3 oster printf("[region %d waiting on reintegration]\n", regionID); 624 1.3 oster /* XXX not sure about the use of finish - shouldn't this 625 1.3 oster * always be "Enqueue"? */ 626 1.3 oster if (finish) 627 1.3 oster RequeueParityLogData(logData, &raidPtr->parityLogDiskQueue.reintBlockHead, &raidPtr->parityLogDiskQueue.reintBlockTail); 628 1.3 oster else 629 1.3 oster EnqueueParityLogData(logData, &raidPtr->parityLogDiskQueue.reintBlockHead, &raidPtr->parityLogDiskQueue.reintBlockTail); 630 1.16 mrg rf_unlock_mutex2(raidPtr->regionInfo[regionID].reintMutex); 631 1.3 oster return (1); /* relenquish control of this thread */ 632 1.3 oster } 633 1.16 mrg rf_unlock_mutex2(raidPtr->regionInfo[regionID].reintMutex); 634 1.3 oster raidPtr->regionInfo[regionID].coreLog = NULL; 635 1.3 oster if ((raidPtr->regionInfo[regionID].diskCount) < raidPtr->regionInfo[regionID].capacity) 636 1.3 oster /* IMPORTANT!! this loop bound assumes region disk holds an 637 1.3 oster * integral number of core logs */ 638 1.3 oster { 639 1.3 oster /* update disk map for this region */ 640 1.3 oster diskCount = raidPtr->regionInfo[regionID].diskCount; 641 1.3 oster for (i = 0; i < raidPtr->numSectorsPerLog; i++) { 642 1.3 oster raidPtr->regionInfo[regionID].diskMap[i + diskCount].operation = log->records[i].operation; 643 1.3 oster raidPtr->regionInfo[regionID].diskMap[i + diskCount].parityAddr = log->records[i].parityAddr; 644 1.3 oster } 645 1.3 oster log->diskOffset = diskCount; 646 1.3 oster raidPtr->regionInfo[regionID].diskCount += raidPtr->numSectorsPerLog; 647 1.3 oster FlushLog(raidPtr, log); 648 1.3 oster } else { 649 1.3 oster /* no room for log on disk, send it to disk manager and 650 1.3 oster * request reintegration */ 651 1.3 oster RF_ASSERT(raidPtr->regionInfo[regionID].diskCount == raidPtr->regionInfo[regionID].capacity); 652 1.3 oster ReintLog(raidPtr, regionID, log); 653 1.3 oster } 654 1.3 oster if (rf_parityLogDebug) 655 1.3 oster printf("[finished dumping parity log to disk, region %d]\n", regionID); 656 1.3 oster return (0); 657 1.3 oster } 658 1.3 oster 659 1.10 perry int 660 1.3 oster rf_ParityLogAppend( 661 1.3 oster RF_ParityLogData_t * logData, 662 1.3 oster int finish, 663 1.3 oster RF_ParityLog_t ** incomingLog, 664 1.3 oster int clearReintFlag) 665 1.3 oster { 666 1.3 oster int regionID, logItem, itemDone; 667 1.3 oster RF_ParityLogData_t *item; 668 1.3 oster int punt, done = RF_FALSE; 669 1.3 oster RF_ParityLog_t *log; 670 1.3 oster RF_Raid_t *raidPtr; 671 1.3 oster RF_Etimer_t timer; 672 1.20 christos void (*wakeFunc) (void *, int); 673 1.3 oster void *wakeArg; 674 1.3 oster 675 1.3 oster /* Add parity to the appropriate log, one sector at a time. This 676 1.3 oster * routine is called is called by dag functions ParityLogUpdateFunc 677 1.3 oster * and ParityLogOverwriteFunc and therefore MUST BE NONBLOCKING. 678 1.10 perry * 679 1.3 oster * Parity to be logged is contained in a linked-list (logData). When 680 1.3 oster * this routine returns, every sector in the list will be in one of 681 1.3 oster * three places: 1) entered into the parity log 2) queued, waiting on 682 1.3 oster * reintegration 3) queued, waiting on a core log 683 1.10 perry * 684 1.3 oster * Blocked work is passed to the ParityLoggingDiskManager for completion. 685 1.3 oster * Later, as conditions which required the block are removed, the work 686 1.3 oster * reenters this routine with the "finish" parameter set to "RF_TRUE." 687 1.10 perry * 688 1.3 oster * NON-BLOCKING */ 689 1.3 oster 690 1.3 oster raidPtr = logData->common->raidPtr; 691 1.3 oster /* lock the region for the first item in logData */ 692 1.3 oster RF_ASSERT(logData != NULL); 693 1.3 oster regionID = logData->regionID; 694 1.17 mrg rf_lock_mutex2(raidPtr->regionInfo[regionID].mutex); 695 1.3 oster RF_ASSERT(raidPtr->regionInfo[regionID].loggingEnabled); 696 1.3 oster 697 1.3 oster if (clearReintFlag) { 698 1.3 oster /* Enable flushing for this region. Holding both locks 699 1.3 oster * provides a synchronization barrier with DumpParityLogToDisk */ 700 1.16 mrg rf_lock_mutex2(raidPtr->regionInfo[regionID].reintMutex); 701 1.15 mrg /* XXXmrg need this? */ 702 1.15 mrg rf_lock_mutex2(raidPtr->parityLogDiskQueue.mutex); 703 1.3 oster RF_ASSERT(raidPtr->regionInfo[regionID].reintInProgress == RF_TRUE); 704 1.3 oster raidPtr->regionInfo[regionID].diskCount = 0; 705 1.3 oster raidPtr->regionInfo[regionID].reintInProgress = RF_FALSE; 706 1.16 mrg rf_unlock_mutex2(raidPtr->regionInfo[regionID].reintMutex); /* flushing is now 707 1.3 oster * enabled */ 708 1.15 mrg /* XXXmrg need this? */ 709 1.15 mrg rf_unlock_mutex2(raidPtr->parityLogDiskQueue.mutex); 710 1.3 oster } 711 1.3 oster /* process each item in logData */ 712 1.3 oster while (logData) { 713 1.3 oster /* remove an item from logData */ 714 1.3 oster item = logData; 715 1.3 oster logData = logData->next; 716 1.3 oster item->next = NULL; 717 1.3 oster item->prev = NULL; 718 1.3 oster 719 1.3 oster if (rf_parityLogDebug) 720 1.3 oster printf("[appending parity log data, region %d, raidAddress %d, numSector %d]\n", item->regionID, (int) item->diskAddress.raidAddress, (int) item->diskAddress.numSector); 721 1.3 oster 722 1.3 oster /* see if we moved to a new region */ 723 1.3 oster if (regionID != item->regionID) { 724 1.17 mrg rf_unlock_mutex2(raidPtr->regionInfo[regionID].mutex); 725 1.3 oster regionID = item->regionID; 726 1.17 mrg rf_lock_mutex2(raidPtr->regionInfo[regionID].mutex); 727 1.3 oster RF_ASSERT(raidPtr->regionInfo[regionID].loggingEnabled); 728 1.3 oster } 729 1.3 oster punt = RF_FALSE;/* Set to RF_TRUE if work is blocked. This 730 1.3 oster * can happen in one of two ways: 1) no core 731 1.3 oster * log (AcquireParityLog) 2) waiting on 732 1.3 oster * reintegration (DumpParityLogToDisk) If punt 733 1.3 oster * is RF_TRUE, the dataItem was queued, so 734 1.3 oster * skip to next item. */ 735 1.3 oster 736 1.3 oster /* process item, one sector at a time, until all sectors 737 1.3 oster * processed or we punt */ 738 1.3 oster if (item->diskAddress.numSector > 0) 739 1.3 oster done = RF_FALSE; 740 1.3 oster else 741 1.3 oster RF_ASSERT(0); 742 1.3 oster while (!punt && !done) { 743 1.3 oster /* verify that a core log exists for this region */ 744 1.3 oster if (!raidPtr->regionInfo[regionID].coreLog) { 745 1.3 oster /* Attempt to acquire a parity log. If 746 1.3 oster * acquisition fails, queue remaining work in 747 1.3 oster * data item and move to nextItem. */ 748 1.3 oster if (incomingLog) 749 1.3 oster if (*incomingLog) { 750 1.3 oster RF_ASSERT((*incomingLog)->next == NULL); 751 1.3 oster raidPtr->regionInfo[regionID].coreLog = *incomingLog; 752 1.3 oster raidPtr->regionInfo[regionID].coreLog->regionID = regionID; 753 1.3 oster *incomingLog = NULL; 754 1.3 oster } else 755 1.3 oster raidPtr->regionInfo[regionID].coreLog = AcquireParityLog(item, finish); 756 1.3 oster else 757 1.3 oster raidPtr->regionInfo[regionID].coreLog = AcquireParityLog(item, finish); 758 1.3 oster /* Note: AcquireParityLog either returns a log 759 1.3 oster * or enqueues currentItem */ 760 1.3 oster } 761 1.3 oster if (!raidPtr->regionInfo[regionID].coreLog) 762 1.3 oster punt = RF_TRUE; /* failed to find a core log */ 763 1.3 oster else { 764 1.3 oster RF_ASSERT(raidPtr->regionInfo[regionID].coreLog->next == NULL); 765 1.3 oster /* verify that the log has room for new 766 1.3 oster * entries */ 767 1.3 oster /* if log is full, dump it to disk and grab a 768 1.3 oster * new log */ 769 1.3 oster if (raidPtr->regionInfo[regionID].coreLog->numRecords == raidPtr->numSectorsPerLog) { 770 1.3 oster /* log is full, dump it to disk */ 771 1.3 oster if (DumpParityLogToDisk(finish, item)) 772 1.3 oster punt = RF_TRUE; /* dump unsuccessful, 773 1.3 oster * blocked on 774 1.3 oster * reintegration */ 775 1.3 oster else { 776 1.3 oster /* dump was successful */ 777 1.3 oster if (incomingLog) 778 1.3 oster if (*incomingLog) { 779 1.3 oster RF_ASSERT((*incomingLog)->next == NULL); 780 1.3 oster raidPtr->regionInfo[regionID].coreLog = *incomingLog; 781 1.3 oster raidPtr->regionInfo[regionID].coreLog->regionID = regionID; 782 1.3 oster *incomingLog = NULL; 783 1.3 oster } else 784 1.3 oster raidPtr->regionInfo[regionID].coreLog = AcquireParityLog(item, finish); 785 1.3 oster else 786 1.3 oster raidPtr->regionInfo[regionID].coreLog = AcquireParityLog(item, finish); 787 1.3 oster /* if a core log is not 788 1.3 oster * available, must queue work 789 1.3 oster * and return */ 790 1.3 oster if (!raidPtr->regionInfo[regionID].coreLog) 791 1.3 oster punt = RF_TRUE; /* blocked on log 792 1.3 oster * availability */ 793 1.3 oster } 794 1.3 oster } 795 1.3 oster } 796 1.3 oster /* if we didn't punt on this item, attempt to add a 797 1.3 oster * sector to the core log */ 798 1.3 oster if (!punt) { 799 1.3 oster RF_ASSERT(raidPtr->regionInfo[regionID].coreLog->next == NULL); 800 1.3 oster /* at this point, we have a core log with 801 1.3 oster * enough room for a sector */ 802 1.3 oster /* copy a sector into the log */ 803 1.3 oster log = raidPtr->regionInfo[regionID].coreLog; 804 1.3 oster RF_ASSERT(log->numRecords < raidPtr->numSectorsPerLog); 805 1.3 oster logItem = log->numRecords++; 806 1.3 oster log->records[logItem].parityAddr = item->diskAddress; 807 1.3 oster RF_ASSERT(log->records[logItem].parityAddr.startSector >= raidPtr->regionInfo[regionID].parityStartAddr); 808 1.3 oster RF_ASSERT(log->records[logItem].parityAddr.startSector < raidPtr->regionInfo[regionID].parityStartAddr + raidPtr->regionInfo[regionID].numSectorsParity); 809 1.3 oster log->records[logItem].parityAddr.numSector = 1; 810 1.3 oster log->records[logItem].operation = item->common->operation; 811 1.13 christos memcpy((char *)log->bufPtr + (logItem * (1 << item->common->raidPtr->logBytesPerSector)), ((char *)item->common->bufPtr + (item->bufOffset++ * (1 << item->common->raidPtr->logBytesPerSector))), (1 << item->common->raidPtr->logBytesPerSector)); 812 1.3 oster item->diskAddress.numSector--; 813 1.3 oster item->diskAddress.startSector++; 814 1.3 oster if (item->diskAddress.numSector == 0) 815 1.3 oster done = RF_TRUE; 816 1.3 oster } 817 1.3 oster } 818 1.1 oster 819 1.3 oster if (!punt) { 820 1.3 oster /* Processed this item completely, decrement count of 821 1.3 oster * items to be processed. */ 822 1.3 oster RF_ASSERT(item->diskAddress.numSector == 0); 823 1.14 mrg rf_lock_mutex2(item->common->mutex); 824 1.3 oster item->common->cnt--; 825 1.3 oster if (item->common->cnt == 0) 826 1.3 oster itemDone = RF_TRUE; 827 1.1 oster else 828 1.3 oster itemDone = RF_FALSE; 829 1.14 mrg rf_unlock_mutex2(item->common->mutex); 830 1.3 oster if (itemDone) { 831 1.3 oster /* Finished processing all log data for this 832 1.3 oster * IO Return structs to free list and invoke 833 1.3 oster * wakeup function. */ 834 1.3 oster timer = item->common->startTime; /* grab initial value of 835 1.3 oster * timer */ 836 1.3 oster RF_ETIMER_STOP(timer); 837 1.3 oster RF_ETIMER_EVAL(timer); 838 1.3 oster item->common->tracerec->plog_us += RF_ETIMER_VAL_US(timer); 839 1.3 oster if (rf_parityLogDebug) 840 1.3 oster printf("[waking process for region %d]\n", item->regionID); 841 1.3 oster wakeFunc = item->common->wakeFunc; 842 1.3 oster wakeArg = item->common->wakeArg; 843 1.3 oster FreeParityLogCommonData(item->common); 844 1.3 oster FreeParityLogData(item); 845 1.3 oster (wakeFunc) (wakeArg, 0); 846 1.3 oster } else 847 1.3 oster FreeParityLogData(item); 848 1.3 oster } 849 1.1 oster } 850 1.17 mrg rf_unlock_mutex2(raidPtr->regionInfo[regionID].mutex); 851 1.3 oster if (rf_parityLogDebug) 852 1.3 oster printf("[exiting ParityLogAppend]\n"); 853 1.3 oster return (0); 854 1.3 oster } 855 1.1 oster 856 1.1 oster 857 1.10 perry void 858 1.3 oster rf_EnableParityLogging(RF_Raid_t * raidPtr) 859 1.3 oster { 860 1.3 oster int regionID; 861 1.1 oster 862 1.3 oster for (regionID = 0; regionID < rf_numParityRegions; regionID++) { 863 1.17 mrg rf_lock_mutex2(raidPtr->regionInfo[regionID].mutex); 864 1.3 oster raidPtr->regionInfo[regionID].loggingEnabled = RF_TRUE; 865 1.17 mrg rf_unlock_mutex2(raidPtr->regionInfo[regionID].mutex); 866 1.3 oster } 867 1.3 oster if (rf_parityLogDebug) 868 1.3 oster printf("[parity logging enabled]\n"); 869 1.1 oster } 870 1.3 oster #endif /* RF_INCLUDE_PARITYLOGGING > 0 */ 871