1309260Scognet/* 2309260Scognet * Copyright 2010-2015 Samy Al Bahra. 3309260Scognet * Copyright 2011 David Joseph. 4309260Scognet * All rights reserved. 5309260Scognet * 6309260Scognet * Redistribution and use in source and binary forms, with or without 7309260Scognet * modification, are permitted provided that the following conditions 8309260Scognet * are met: 9309260Scognet * 1. Redistributions of source code must retain the above copyright 10309260Scognet * notice, this list of conditions and the following disclaimer. 11309260Scognet * 2. Redistributions in binary form must reproduce the above copyright 12309260Scognet * notice, this list of conditions and the following disclaimer in the 13309260Scognet * documentation and/or other materials provided with the distribution. 14309260Scognet * 15309260Scognet * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16309260Scognet * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17309260Scognet * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18309260Scognet * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19309260Scognet * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20309260Scognet * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21309260Scognet * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22309260Scognet * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23309260Scognet * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24309260Scognet * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25309260Scognet * SUCH DAMAGE. 26309260Scognet */ 27309260Scognet 28309260Scognet#ifndef CK_FIFO_H 29309260Scognet#define CK_FIFO_H 30309260Scognet 31309260Scognet#include <ck_cc.h> 32309260Scognet#include <ck_md.h> 33309260Scognet#include <ck_pr.h> 34309260Scognet#include <ck_spinlock.h> 35309260Scognet#include <ck_stddef.h> 36309260Scognet 37309260Scognet#ifndef CK_F_FIFO_SPSC 38309260Scognet#define CK_F_FIFO_SPSC 39309260Scognetstruct ck_fifo_spsc_entry { 40309260Scognet void *value; 41309260Scognet struct ck_fifo_spsc_entry *next; 42309260Scognet}; 43309260Scognettypedef struct ck_fifo_spsc_entry ck_fifo_spsc_entry_t; 44309260Scognet 45309260Scognetstruct ck_fifo_spsc { 46309260Scognet ck_spinlock_t m_head; 47309260Scognet struct ck_fifo_spsc_entry *head; 48309260Scognet char pad[CK_MD_CACHELINE - sizeof(struct ck_fifo_spsc_entry *) - sizeof(ck_spinlock_t)]; 49309260Scognet ck_spinlock_t m_tail; 50309260Scognet struct ck_fifo_spsc_entry *tail; 51309260Scognet struct ck_fifo_spsc_entry *head_snapshot; 52309260Scognet struct ck_fifo_spsc_entry *garbage; 53309260Scognet}; 54309260Scognettypedef struct ck_fifo_spsc ck_fifo_spsc_t; 55309260Scognet 56309260ScognetCK_CC_INLINE static bool 57309260Scognetck_fifo_spsc_enqueue_trylock(struct ck_fifo_spsc *fifo) 58309260Scognet{ 59309260Scognet 60309260Scognet return ck_spinlock_trylock(&fifo->m_tail); 61309260Scognet} 62309260Scognet 63309260ScognetCK_CC_INLINE static void 64309260Scognetck_fifo_spsc_enqueue_lock(struct ck_fifo_spsc *fifo) 65309260Scognet{ 66309260Scognet 67309260Scognet ck_spinlock_lock(&fifo->m_tail); 68309260Scognet return; 69309260Scognet} 70309260Scognet 71309260ScognetCK_CC_INLINE static void 72309260Scognetck_fifo_spsc_enqueue_unlock(struct ck_fifo_spsc *fifo) 73309260Scognet{ 74309260Scognet 75309260Scognet ck_spinlock_unlock(&fifo->m_tail); 76309260Scognet return; 77309260Scognet} 78309260Scognet 79309260ScognetCK_CC_INLINE static bool 80309260Scognetck_fifo_spsc_dequeue_trylock(struct ck_fifo_spsc *fifo) 81309260Scognet{ 82309260Scognet 83309260Scognet return ck_spinlock_trylock(&fifo->m_head); 84309260Scognet} 85309260Scognet 86309260ScognetCK_CC_INLINE static void 87309260Scognetck_fifo_spsc_dequeue_lock(struct ck_fifo_spsc *fifo) 88309260Scognet{ 89309260Scognet 90309260Scognet ck_spinlock_lock(&fifo->m_head); 91309260Scognet return; 92309260Scognet} 93309260Scognet 94309260ScognetCK_CC_INLINE static void 95309260Scognetck_fifo_spsc_dequeue_unlock(struct ck_fifo_spsc *fifo) 96309260Scognet{ 97309260Scognet 98309260Scognet ck_spinlock_unlock(&fifo->m_head); 99309260Scognet return; 100309260Scognet} 101309260Scognet 102309260ScognetCK_CC_INLINE static void 103309260Scognetck_fifo_spsc_init(struct ck_fifo_spsc *fifo, struct ck_fifo_spsc_entry *stub) 104309260Scognet{ 105309260Scognet 106309260Scognet ck_spinlock_init(&fifo->m_head); 107309260Scognet ck_spinlock_init(&fifo->m_tail); 108309260Scognet 109309260Scognet stub->next = NULL; 110309260Scognet fifo->head = fifo->tail = fifo->head_snapshot = fifo->garbage = stub; 111309260Scognet return; 112309260Scognet} 113309260Scognet 114309260ScognetCK_CC_INLINE static void 115309260Scognetck_fifo_spsc_deinit(struct ck_fifo_spsc *fifo, struct ck_fifo_spsc_entry **garbage) 116309260Scognet{ 117309260Scognet 118309260Scognet *garbage = fifo->head; 119309260Scognet fifo->head = fifo->tail = NULL; 120309260Scognet return; 121309260Scognet} 122309260Scognet 123309260ScognetCK_CC_INLINE static void 124309260Scognetck_fifo_spsc_enqueue(struct ck_fifo_spsc *fifo, 125309260Scognet struct ck_fifo_spsc_entry *entry, 126309260Scognet void *value) 127309260Scognet{ 128309260Scognet 129309260Scognet entry->value = value; 130309260Scognet entry->next = NULL; 131309260Scognet 132309260Scognet /* If stub->next is visible, guarantee that entry is consistent. */ 133309260Scognet ck_pr_fence_store(); 134309260Scognet ck_pr_store_ptr(&fifo->tail->next, entry); 135309260Scognet fifo->tail = entry; 136309260Scognet return; 137309260Scognet} 138309260Scognet 139309260ScognetCK_CC_INLINE static bool 140309260Scognetck_fifo_spsc_dequeue(struct ck_fifo_spsc *fifo, void *value) 141309260Scognet{ 142309260Scognet struct ck_fifo_spsc_entry *entry; 143309260Scognet 144309260Scognet /* 145309260Scognet * The head pointer is guaranteed to always point to a stub entry. 146309260Scognet * If the stub entry does not point to an entry, then the queue is 147309260Scognet * empty. 148309260Scognet */ 149309260Scognet entry = ck_pr_load_ptr(&fifo->head->next); 150309260Scognet if (entry == NULL) 151309260Scognet return false; 152309260Scognet 153309260Scognet /* If entry is visible, guarantee store to value is visible. */ 154309260Scognet ck_pr_store_ptr_unsafe(value, entry->value); 155309260Scognet ck_pr_fence_store(); 156309260Scognet ck_pr_store_ptr(&fifo->head, entry); 157309260Scognet return true; 158309260Scognet} 159309260Scognet 160309260Scognet/* 161309260Scognet * Recycle a node. This technique for recycling nodes is based on 162309260Scognet * Dmitriy Vyukov's work. 163309260Scognet */ 164309260ScognetCK_CC_INLINE static struct ck_fifo_spsc_entry * 165309260Scognetck_fifo_spsc_recycle(struct ck_fifo_spsc *fifo) 166309260Scognet{ 167309260Scognet struct ck_fifo_spsc_entry *garbage; 168309260Scognet 169309260Scognet if (fifo->head_snapshot == fifo->garbage) { 170309260Scognet fifo->head_snapshot = ck_pr_load_ptr(&fifo->head); 171309260Scognet if (fifo->head_snapshot == fifo->garbage) 172309260Scognet return NULL; 173309260Scognet } 174309260Scognet 175309260Scognet garbage = fifo->garbage; 176309260Scognet fifo->garbage = garbage->next; 177309260Scognet return garbage; 178309260Scognet} 179309260Scognet 180309260ScognetCK_CC_INLINE static bool 181309260Scognetck_fifo_spsc_isempty(struct ck_fifo_spsc *fifo) 182309260Scognet{ 183309260Scognet struct ck_fifo_spsc_entry *head = ck_pr_load_ptr(&fifo->head); 184309260Scognet return ck_pr_load_ptr(&head->next) == NULL; 185309260Scognet} 186309260Scognet 187309260Scognet#define CK_FIFO_SPSC_ISEMPTY(f) ((f)->head->next == NULL) 188309260Scognet#define CK_FIFO_SPSC_FIRST(f) ((f)->head->next) 189309260Scognet#define CK_FIFO_SPSC_NEXT(m) ((m)->next) 190309260Scognet#define CK_FIFO_SPSC_SPARE(f) ((f)->head) 191309260Scognet#define CK_FIFO_SPSC_FOREACH(fifo, entry) \ 192309260Scognet for ((entry) = CK_FIFO_SPSC_FIRST(fifo); \ 193309260Scognet (entry) != NULL; \ 194309260Scognet (entry) = CK_FIFO_SPSC_NEXT(entry)) 195309260Scognet#define CK_FIFO_SPSC_FOREACH_SAFE(fifo, entry, T) \ 196309260Scognet for ((entry) = CK_FIFO_SPSC_FIRST(fifo); \ 197309260Scognet (entry) != NULL && ((T) = (entry)->next, 1); \ 198309260Scognet (entry) = (T)) 199309260Scognet 200309260Scognet#endif /* CK_F_FIFO_SPSC */ 201309260Scognet 202309260Scognet#ifdef CK_F_PR_CAS_PTR_2 203309260Scognet#ifndef CK_F_FIFO_MPMC 204309260Scognet#define CK_F_FIFO_MPMC 205309260Scognetstruct ck_fifo_mpmc_entry; 206309260Scognetstruct ck_fifo_mpmc_pointer { 207309260Scognet struct ck_fifo_mpmc_entry *pointer; 208309260Scognet char *generation CK_CC_PACKED; 209309260Scognet} CK_CC_ALIGN(16); 210309260Scognet 211309260Scognetstruct ck_fifo_mpmc_entry { 212309260Scognet void *value; 213309260Scognet struct ck_fifo_mpmc_pointer next; 214309260Scognet}; 215309260Scognettypedef struct ck_fifo_mpmc_entry ck_fifo_mpmc_entry_t; 216309260Scognet 217309260Scognetstruct ck_fifo_mpmc { 218309260Scognet struct ck_fifo_mpmc_pointer head; 219309260Scognet char pad[CK_MD_CACHELINE - sizeof(struct ck_fifo_mpmc_pointer)]; 220309260Scognet struct ck_fifo_mpmc_pointer tail; 221309260Scognet}; 222309260Scognettypedef struct ck_fifo_mpmc ck_fifo_mpmc_t; 223309260Scognet 224309260ScognetCK_CC_INLINE static void 225309260Scognetck_fifo_mpmc_init(struct ck_fifo_mpmc *fifo, struct ck_fifo_mpmc_entry *stub) 226309260Scognet{ 227309260Scognet 228309260Scognet stub->next.pointer = NULL; 229309260Scognet stub->next.generation = NULL; 230309260Scognet fifo->head.pointer = fifo->tail.pointer = stub; 231309260Scognet fifo->head.generation = fifo->tail.generation = NULL; 232309260Scognet return; 233309260Scognet} 234309260Scognet 235309260ScognetCK_CC_INLINE static void 236309260Scognetck_fifo_mpmc_deinit(struct ck_fifo_mpmc *fifo, struct ck_fifo_mpmc_entry **garbage) 237309260Scognet{ 238309260Scognet 239309260Scognet *garbage = fifo->head.pointer; 240309260Scognet fifo->head.pointer = fifo->tail.pointer = NULL; 241309260Scognet return; 242309260Scognet} 243309260Scognet 244309260ScognetCK_CC_INLINE static void 245309260Scognetck_fifo_mpmc_enqueue(struct ck_fifo_mpmc *fifo, 246309260Scognet struct ck_fifo_mpmc_entry *entry, 247309260Scognet void *value) 248309260Scognet{ 249309260Scognet struct ck_fifo_mpmc_pointer tail, next, update; 250309260Scognet 251309260Scognet /* 252309260Scognet * Prepare the upcoming node and make sure to commit the updates 253309260Scognet * before publishing. 254309260Scognet */ 255309260Scognet entry->value = value; 256309260Scognet entry->next.pointer = NULL; 257309260Scognet entry->next.generation = 0; 258309260Scognet ck_pr_fence_store_atomic(); 259309260Scognet 260309260Scognet for (;;) { 261309260Scognet tail.generation = ck_pr_load_ptr(&fifo->tail.generation); 262309260Scognet ck_pr_fence_load(); 263309260Scognet tail.pointer = ck_pr_load_ptr(&fifo->tail.pointer); 264309260Scognet next.generation = ck_pr_load_ptr(&tail.pointer->next.generation); 265309260Scognet ck_pr_fence_load(); 266309260Scognet next.pointer = ck_pr_load_ptr(&tail.pointer->next.pointer); 267309260Scognet 268309260Scognet if (ck_pr_load_ptr(&fifo->tail.generation) != tail.generation) 269309260Scognet continue; 270309260Scognet 271309260Scognet if (next.pointer != NULL) { 272309260Scognet /* 273309260Scognet * If the tail pointer has an entry following it then 274309260Scognet * it needs to be forwarded to the next entry. This 275309260Scognet * helps us guarantee we are always operating on the 276309260Scognet * last entry. 277309260Scognet */ 278309260Scognet update.pointer = next.pointer; 279309260Scognet update.generation = tail.generation + 1; 280309260Scognet ck_pr_cas_ptr_2(&fifo->tail, &tail, &update); 281309260Scognet } else { 282309260Scognet /* 283309260Scognet * Attempt to commit new entry to the end of the 284309260Scognet * current tail. 285309260Scognet */ 286309260Scognet update.pointer = entry; 287309260Scognet update.generation = next.generation + 1; 288309260Scognet if (ck_pr_cas_ptr_2(&tail.pointer->next, &next, &update) == true) 289309260Scognet break; 290309260Scognet } 291309260Scognet } 292309260Scognet 293309260Scognet ck_pr_fence_atomic(); 294309260Scognet 295309260Scognet /* After a successful insert, forward the tail to the new entry. */ 296309260Scognet update.generation = tail.generation + 1; 297309260Scognet ck_pr_cas_ptr_2(&fifo->tail, &tail, &update); 298309260Scognet return; 299309260Scognet} 300309260Scognet 301309260ScognetCK_CC_INLINE static bool 302309260Scognetck_fifo_mpmc_tryenqueue(struct ck_fifo_mpmc *fifo, 303309260Scognet struct ck_fifo_mpmc_entry *entry, 304309260Scognet void *value) 305309260Scognet{ 306309260Scognet struct ck_fifo_mpmc_pointer tail, next, update; 307309260Scognet 308309260Scognet entry->value = value; 309309260Scognet entry->next.pointer = NULL; 310309260Scognet entry->next.generation = 0; 311309260Scognet 312309260Scognet ck_pr_fence_store_atomic(); 313309260Scognet 314309260Scognet tail.generation = ck_pr_load_ptr(&fifo->tail.generation); 315309260Scognet ck_pr_fence_load(); 316309260Scognet tail.pointer = ck_pr_load_ptr(&fifo->tail.pointer); 317309260Scognet next.generation = ck_pr_load_ptr(&tail.pointer->next.generation); 318309260Scognet ck_pr_fence_load(); 319309260Scognet next.pointer = ck_pr_load_ptr(&tail.pointer->next.pointer); 320309260Scognet 321309260Scognet if (ck_pr_load_ptr(&fifo->tail.generation) != tail.generation) 322309260Scognet return false; 323309260Scognet 324309260Scognet if (next.pointer != NULL) { 325309260Scognet /* 326309260Scognet * If the tail pointer has an entry following it then 327309260Scognet * it needs to be forwarded to the next entry. This 328309260Scognet * helps us guarantee we are always operating on the 329309260Scognet * last entry. 330309260Scognet */ 331309260Scognet update.pointer = next.pointer; 332309260Scognet update.generation = tail.generation + 1; 333309260Scognet ck_pr_cas_ptr_2(&fifo->tail, &tail, &update); 334309260Scognet return false; 335309260Scognet } else { 336309260Scognet /* 337309260Scognet * Attempt to commit new entry to the end of the 338309260Scognet * current tail. 339309260Scognet */ 340309260Scognet update.pointer = entry; 341309260Scognet update.generation = next.generation + 1; 342309260Scognet if (ck_pr_cas_ptr_2(&tail.pointer->next, &next, &update) == false) 343309260Scognet return false; 344309260Scognet } 345309260Scognet 346309260Scognet ck_pr_fence_atomic(); 347309260Scognet 348309260Scognet /* After a successful insert, forward the tail to the new entry. */ 349309260Scognet update.generation = tail.generation + 1; 350309260Scognet ck_pr_cas_ptr_2(&fifo->tail, &tail, &update); 351309260Scognet return true; 352309260Scognet} 353309260Scognet 354309260ScognetCK_CC_INLINE static bool 355309260Scognetck_fifo_mpmc_dequeue(struct ck_fifo_mpmc *fifo, 356309260Scognet void *value, 357309260Scognet struct ck_fifo_mpmc_entry **garbage) 358309260Scognet{ 359309260Scognet struct ck_fifo_mpmc_pointer head, tail, next, update; 360309260Scognet 361309260Scognet for (;;) { 362309260Scognet head.generation = ck_pr_load_ptr(&fifo->head.generation); 363309260Scognet ck_pr_fence_load(); 364309260Scognet head.pointer = ck_pr_load_ptr(&fifo->head.pointer); 365309260Scognet tail.generation = ck_pr_load_ptr(&fifo->tail.generation); 366309260Scognet ck_pr_fence_load(); 367309260Scognet tail.pointer = ck_pr_load_ptr(&fifo->tail.pointer); 368309260Scognet 369309260Scognet next.generation = ck_pr_load_ptr(&head.pointer->next.generation); 370309260Scognet ck_pr_fence_load(); 371309260Scognet next.pointer = ck_pr_load_ptr(&head.pointer->next.pointer); 372309260Scognet 373309260Scognet update.pointer = next.pointer; 374309260Scognet if (head.pointer == tail.pointer) { 375309260Scognet /* 376309260Scognet * The head is guaranteed to always point at a stub 377309260Scognet * entry. If the stub entry has no references then the 378309260Scognet * queue is empty. 379309260Scognet */ 380309260Scognet if (next.pointer == NULL) 381309260Scognet return false; 382309260Scognet 383309260Scognet /* Forward the tail pointer if necessary. */ 384309260Scognet update.generation = tail.generation + 1; 385309260Scognet ck_pr_cas_ptr_2(&fifo->tail, &tail, &update); 386309260Scognet } else { 387309260Scognet /* 388309260Scognet * It is possible for head snapshot to have been 389309260Scognet * re-used. Avoid deferencing during enqueue 390309260Scognet * re-use. 391309260Scognet */ 392309260Scognet if (next.pointer == NULL) 393309260Scognet continue; 394309260Scognet 395309260Scognet /* Save value before commit. */ 396309260Scognet *(void **)value = ck_pr_load_ptr(&next.pointer->value); 397309260Scognet 398309260Scognet /* Forward the head pointer to the next entry. */ 399309260Scognet update.generation = head.generation + 1; 400309260Scognet if (ck_pr_cas_ptr_2(&fifo->head, &head, &update) == true) 401309260Scognet break; 402309260Scognet } 403309260Scognet } 404309260Scognet 405309260Scognet *garbage = head.pointer; 406309260Scognet return true; 407309260Scognet} 408309260Scognet 409309260ScognetCK_CC_INLINE static bool 410309260Scognetck_fifo_mpmc_trydequeue(struct ck_fifo_mpmc *fifo, 411309260Scognet void *value, 412309260Scognet struct ck_fifo_mpmc_entry **garbage) 413309260Scognet{ 414309260Scognet struct ck_fifo_mpmc_pointer head, tail, next, update; 415309260Scognet 416309260Scognet head.generation = ck_pr_load_ptr(&fifo->head.generation); 417309260Scognet ck_pr_fence_load(); 418309260Scognet head.pointer = ck_pr_load_ptr(&fifo->head.pointer); 419309260Scognet 420309260Scognet tail.generation = ck_pr_load_ptr(&fifo->tail.generation); 421309260Scognet ck_pr_fence_load(); 422309260Scognet tail.pointer = ck_pr_load_ptr(&fifo->tail.pointer); 423309260Scognet 424309260Scognet next.generation = ck_pr_load_ptr(&head.pointer->next.generation); 425309260Scognet ck_pr_fence_load(); 426309260Scognet next.pointer = ck_pr_load_ptr(&head.pointer->next.pointer); 427309260Scognet 428309260Scognet update.pointer = next.pointer; 429309260Scognet if (head.pointer == tail.pointer) { 430309260Scognet /* 431309260Scognet * The head is guaranteed to always point at a stub 432309260Scognet * entry. If the stub entry has no references then the 433309260Scognet * queue is empty. 434309260Scognet */ 435309260Scognet if (next.pointer == NULL) 436309260Scognet return false; 437309260Scognet 438309260Scognet /* Forward the tail pointer if necessary. */ 439309260Scognet update.generation = tail.generation + 1; 440309260Scognet ck_pr_cas_ptr_2(&fifo->tail, &tail, &update); 441309260Scognet return false; 442309260Scognet } else { 443309260Scognet /* 444309260Scognet * It is possible for head snapshot to have been 445309260Scognet * re-used. Avoid deferencing during enqueue. 446309260Scognet */ 447309260Scognet if (next.pointer == NULL) 448309260Scognet return false; 449309260Scognet 450309260Scognet /* Save value before commit. */ 451309260Scognet *(void **)value = ck_pr_load_ptr(&next.pointer->value); 452309260Scognet 453309260Scognet /* Forward the head pointer to the next entry. */ 454309260Scognet update.generation = head.generation + 1; 455309260Scognet if (ck_pr_cas_ptr_2(&fifo->head, &head, &update) == false) 456309260Scognet return false; 457309260Scognet } 458309260Scognet 459309260Scognet *garbage = head.pointer; 460309260Scognet return true; 461309260Scognet} 462309260Scognet 463309260Scognet#define CK_FIFO_MPMC_ISEMPTY(f) ((f)->head.pointer->next.pointer == NULL) 464309260Scognet#define CK_FIFO_MPMC_FIRST(f) ((f)->head.pointer->next.pointer) 465309260Scognet#define CK_FIFO_MPMC_NEXT(m) ((m)->next.pointer) 466309260Scognet#define CK_FIFO_MPMC_FOREACH(fifo, entry) \ 467309260Scognet for ((entry) = CK_FIFO_MPMC_FIRST(fifo); \ 468309260Scognet (entry) != NULL; \ 469309260Scognet (entry) = CK_FIFO_MPMC_NEXT(entry)) 470309260Scognet#define CK_FIFO_MPMC_FOREACH_SAFE(fifo, entry, T) \ 471309260Scognet for ((entry) = CK_FIFO_MPMC_FIRST(fifo); \ 472309260Scognet (entry) != NULL && ((T) = (entry)->next.pointer, 1); \ 473309260Scognet (entry) = (T)) 474309260Scognet 475309260Scognet#endif /* CK_F_FIFO_MPMC */ 476309260Scognet#endif /* CK_F_PR_CAS_PTR_2 */ 477309260Scognet 478309260Scognet#endif /* CK_FIFO_H */ 479