/* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2005-06 Adaptec, Inc. * Copyright (c) 2005-06 Adaptec Inc., Achim Leubner * Copyright (c) 2000 Michael Smith * Copyright (c) 2001 Scott Long * Copyright (c) 2000 BSDi * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "aac_regs.h" #include "aac.h" #include "aac_ioctl.h" struct aac_umem_sge { uint32_t bcount; caddr_t addr; struct aac_cmd acp; }; /* * External functions */ extern int aac_sync_mbcommand(struct aac_softstate *, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t *); extern int aac_cmd_dma_alloc(struct aac_softstate *, struct aac_cmd *, struct buf *, int, int (*)(), caddr_t); extern void aac_free_dmamap(struct aac_cmd *); extern int aac_do_io(struct aac_softstate *, struct aac_cmd *); extern void aac_cmd_fib_copy(struct aac_softstate *, struct aac_cmd *); extern void aac_ioctl_complete(struct aac_softstate *, struct aac_cmd *); extern ddi_device_acc_attr_t aac_acc_attr; extern int aac_check_dma_handle(ddi_dma_handle_t); /* * IOCTL command handling functions */ static int aac_check_revision(struct aac_softstate *, intptr_t, int); static int aac_ioctl_send_fib(struct aac_softstate *, intptr_t, int); static int aac_open_getadapter_fib(struct aac_softstate *, intptr_t, int); static int aac_next_getadapter_fib(struct aac_softstate *, intptr_t, int); static int aac_close_getadapter_fib(struct aac_softstate *, intptr_t); static int aac_send_raw_srb(struct aac_softstate *, dev_t, intptr_t, int); static int aac_get_pci_info(struct aac_softstate *, intptr_t, int); static int aac_query_disk(struct aac_softstate *, intptr_t, int); static int aac_delete_disk(struct aac_softstate *, intptr_t, int); static int aac_supported_features(struct aac_softstate *, intptr_t, int); /* * Warlock directives */ _NOTE(SCHEME_PROTECTS_DATA("unique to each handling function", aac_features aac_pci_info aac_query_disk aac_revision aac_umem_sge)) int aac_do_ioctl(struct aac_softstate *softs, dev_t dev, int cmd, intptr_t arg, int mode) { int status; switch (cmd) { case FSACTL_MINIPORT_REV_CHECK: AACDB_PRINT_IOCTL(softs, "FSACTL_MINIPORT_REV_CHECK"); status = aac_check_revision(softs, arg, mode); break; case FSACTL_SENDFIB: AACDB_PRINT_IOCTL(softs, "FSACTL_SEND_LARGE_FIB"); goto send_fib; case FSACTL_SEND_LARGE_FIB: AACDB_PRINT_IOCTL(softs, "FSACTL_SEND_LARGE_FIB"); send_fib: status = aac_ioctl_send_fib(softs, arg, mode); break; case FSACTL_OPEN_GET_ADAPTER_FIB: AACDB_PRINT_IOCTL(softs, "FSACTL_OPEN_GET_ADAPTER_FIB"); status = aac_open_getadapter_fib(softs, arg, mode); break; case FSACTL_GET_NEXT_ADAPTER_FIB: AACDB_PRINT_IOCTL(softs, "FSACTL_GET_NEXT_ADAPTER_FIB"); status = aac_next_getadapter_fib(softs, arg, mode); break; case FSACTL_CLOSE_GET_ADAPTER_FIB: AACDB_PRINT_IOCTL(softs, "FSACTL_CLOSE_GET_ADAPTER_FIB"); status = aac_close_getadapter_fib(softs, arg); break; case FSACTL_SEND_RAW_SRB: AACDB_PRINT_IOCTL(softs, "FSACTL_SEND_RAW_SRB"); status = aac_send_raw_srb(softs, dev, arg, mode); break; case FSACTL_GET_PCI_INFO: AACDB_PRINT_IOCTL(softs, "FSACTL_GET_PCI_INFO"); status = aac_get_pci_info(softs, arg, mode); break; case FSACTL_QUERY_DISK: AACDB_PRINT_IOCTL(softs, "FSACTL_QUERY_DISK"); status = aac_query_disk(softs, arg, mode); break; case FSACTL_DELETE_DISK: AACDB_PRINT_IOCTL(softs, "FSACTL_DELETE_DISK"); status = aac_delete_disk(softs, arg, mode); break; case FSACTL_GET_FEATURES: AACDB_PRINT_IOCTL(softs, "FSACTL_GET_FEATURES"); status = aac_supported_features(softs, arg, mode); break; default: status = ENOTTY; AACDB_PRINT(softs, CE_WARN, "!IOCTL cmd 0x%x not supported", cmd); break; } return (status); } /*ARGSUSED*/ static int aac_check_revision(struct aac_softstate *softs, intptr_t arg, int mode) { union aac_revision_align un; struct aac_revision *aac_rev = &un.d; DBCALLED(softs, 2); /* Copyin the revision struct from userspace */ if (ddi_copyin((void *)arg, aac_rev, sizeof (struct aac_revision), mode) != 0) return (EFAULT); /* Doctor up the response struct */ aac_rev->compat = 1; aac_rev->version = ((uint32_t)AAC_DRIVER_MAJOR_VERSION << 24) | ((uint32_t)AAC_DRIVER_MINOR_VERSION << 16) | ((uint32_t)AAC_DRIVER_TYPE << 8) | ((uint32_t)AAC_DRIVER_BUGFIX_LEVEL); aac_rev->build = (uint32_t)AAC_DRIVER_BUILD; if (ddi_copyout(aac_rev, (void *)arg, sizeof (struct aac_revision), mode) != 0) return (EFAULT); return (0); } static int aac_send_fib(struct aac_softstate *softs, struct aac_cmd *acp) { int rval; acp->flags |= AAC_CMD_NO_CB | AAC_CMD_SYNC; acp->ac_comp = aac_ioctl_complete; mutex_enter(&softs->io_lock); if (softs->state & AAC_STATE_DEAD) { mutex_exit(&softs->io_lock); return (ENXIO); } rval = aac_do_io(softs, acp); if (rval == TRAN_ACCEPT) { rval = 0; } else if (rval == TRAN_BADPKT) { AACDB_PRINT(softs, CE_CONT, "User SendFib failed ENXIO"); rval = ENXIO; } else if (rval == TRAN_BUSY) { AACDB_PRINT(softs, CE_CONT, "User SendFib failed EBUSY"); rval = EBUSY; } mutex_exit(&softs->io_lock); return (rval); } static int aac_ioctl_send_fib(struct aac_softstate *softs, intptr_t arg, int mode) { int hbalen; struct aac_cmd *acp; struct aac_fib *fibp; uint16_t fib_command; uint32_t fib_xfer_state; uint16_t fib_data_size, fib_size; uint16_t fib_sender_size; int rval; DBCALLED(softs, 2); /* Copy in FIB header */ hbalen = sizeof (struct aac_cmd) + softs->aac_max_fib_size; if ((acp = kmem_zalloc(hbalen, KM_NOSLEEP)) == NULL) return (ENOMEM); fibp = (struct aac_fib *)(acp + 1); acp->fibp = fibp; if (ddi_copyin((void *)arg, fibp, sizeof (struct aac_fib_header), mode) != 0) { rval = EFAULT; goto finish; } fib_xfer_state = LE_32(fibp->Header.XferState); fib_command = LE_16(fibp->Header.Command); fib_data_size = LE_16(fibp->Header.Size); fib_sender_size = LE_16(fibp->Header.SenderSize); fib_size = fib_data_size + sizeof (struct aac_fib_header); if (fib_size < fib_sender_size) fib_size = fib_sender_size; if (fib_size > softs->aac_max_fib_size) { rval = EFAULT; goto finish; } /* Copy in FIB data */ if (ddi_copyin(((struct aac_fib *)arg)->data, fibp->data, fib_data_size, mode) != 0) { rval = EFAULT; goto finish; } acp->fib_size = fib_size; fibp->Header.Size = LE_16(fib_size); AACDB_PRINT_FIB(softs, fibp); /* Process FIB */ if (fib_command == TakeABreakPt) { (void) aac_sync_mbcommand(softs, AAC_BREAKPOINT_REQ, 0, 0, 0, 0, NULL); fibp->Header.XferState = LE_32(0); } else { ASSERT(!(fib_xfer_state & AAC_FIBSTATE_ASYNC)); fibp->Header.XferState = LE_32(fib_xfer_state | \ (AAC_FIBSTATE_FROMHOST | AAC_FIBSTATE_REXPECTED)); acp->timeout = AAC_IOCTL_TIMEOUT; acp->aac_cmd_fib = aac_cmd_fib_copy; if ((rval = aac_send_fib(softs, acp)) != 0) goto finish; } if (acp->flags & AAC_CMD_ERR) { AACDB_PRINT(softs, CE_CONT, "FIB data corrupt"); rval = EIO; goto finish; } if (ddi_copyout(fibp, (void *)arg, acp->fib_size, mode) != 0) { AACDB_PRINT(softs, CE_CONT, "FIB copyout failed"); rval = EFAULT; goto finish; } rval = 0; finish: kmem_free(acp, hbalen); return (rval); } static int aac_open_getadapter_fib(struct aac_softstate *softs, intptr_t arg, int mode) { struct aac_fib_context *fibctx, *ctx; DBCALLED(softs, 2); fibctx = kmem_zalloc(sizeof (struct aac_fib_context), KM_NOSLEEP); if (fibctx == NULL) return (ENOMEM); mutex_enter(&softs->aifq_mutex); /* All elements are already 0, add to queue */ if (softs->fibctx == NULL) { softs->fibctx = fibctx; } else { for (ctx = softs->fibctx; ctx->next; ctx = ctx->next) ; ctx->next = fibctx; fibctx->prev = ctx; } /* Evaluate unique value */ fibctx->unique = (unsigned long)fibctx & 0xfffffffful; ctx = softs->fibctx; while (ctx != fibctx) { if (ctx->unique == fibctx->unique) { fibctx->unique++; ctx = softs->fibctx; } else { ctx = ctx->next; } } /* Set ctx_idx to the oldest AIF */ if (softs->aifq_wrap) { fibctx->ctx_idx = softs->aifq_idx; fibctx->ctx_filled = 1; } mutex_exit(&softs->aifq_mutex); if (ddi_copyout(&fibctx->unique, (void *)arg, sizeof (uint32_t), mode) != 0) return (EFAULT); return (0); } static int aac_return_aif(struct aac_softstate *softs, struct aac_fib_context *ctx, caddr_t uptr, int mode) { int current; current = ctx->ctx_idx; if (current == softs->aifq_idx && !ctx->ctx_filled) return (EAGAIN); /* Empty */ if (ddi_copyout(&softs->aifq[current].d, (void *)uptr, sizeof (struct aac_fib), mode) != 0) return (EFAULT); ctx->ctx_filled = 0; ctx->ctx_idx = (current + 1) % AAC_AIFQ_LENGTH; return (0); } static int aac_next_getadapter_fib(struct aac_softstate *softs, intptr_t arg, int mode) { union aac_get_adapter_fib_align un; struct aac_get_adapter_fib *af = &un.d; struct aac_fib_context *ctx; int rval; DBCALLED(softs, 2); if (ddi_copyin((void *)arg, af, sizeof (*af), mode) != 0) return (EFAULT); mutex_enter(&softs->aifq_mutex); for (ctx = softs->fibctx; ctx; ctx = ctx->next) { if (af->context == ctx->unique) break; } if (ctx) { #ifdef _LP64 rval = aac_return_aif(softs, ctx, (caddr_t)(uint64_t)af->aif_fib, mode); #else rval = aac_return_aif(softs, ctx, (caddr_t)af->aif_fib, mode); #endif if (rval == EAGAIN && af->wait) { AACDB_PRINT(softs, CE_NOTE, "aac_next_getadapter_fib(): waiting for AIF"); rval = cv_wait_sig(&softs->aifv, &softs->aifq_mutex); if (rval > 0) { #ifdef _LP64 rval = aac_return_aif(softs, ctx, (caddr_t)(uint64_t)af->aif_fib, mode); #else rval = aac_return_aif(softs, ctx, (caddr_t)af->aif_fib, mode); #endif } else { rval = EINTR; } } } else { rval = EFAULT; } mutex_exit(&softs->aifq_mutex); return (rval); } static int aac_close_getadapter_fib(struct aac_softstate *softs, intptr_t arg) { struct aac_fib_context *ctx; DBCALLED(softs, 2); mutex_enter(&softs->aifq_mutex); for (ctx = softs->fibctx; ctx; ctx = ctx->next) { if (ctx->unique != (uint32_t)arg) continue; if (ctx == softs->fibctx) softs->fibctx = ctx->next; else ctx->prev->next = ctx->next; if (ctx->next) ctx->next->prev = ctx->prev; break; } mutex_exit(&softs->aifq_mutex); if (ctx) kmem_free(ctx, sizeof (struct aac_fib_context)); return (0); } /* * The following function comes from Adaptec: * * SRB is required for the new management tools * Note: SRB passed down from IOCTL is always in CPU endianness. */ static int aac_send_raw_srb(struct aac_softstate *softs, dev_t dev, intptr_t arg, int mode) { struct aac_cmd *acp; struct aac_fib *fibp; struct aac_srb *srb; uint32_t usr_fib_size; uint32_t srb_sgcount; struct aac_umem_sge *usgt = NULL; struct aac_umem_sge *usge; ddi_umem_cookie_t cookie; int umem_flags = 0; int direct = 0; int locked = 0; caddr_t addrlo = (caddr_t)-1; caddr_t addrhi = 0; struct aac_sge *sge, *sge0; int sg64; int rval; DBCALLED(softs, 2); /* Read srb size */ if (ddi_copyin(&((struct aac_srb *)arg)->count, &usr_fib_size, sizeof (uint32_t), mode) != 0) return (EFAULT); if (usr_fib_size > (softs->aac_max_fib_size - \ sizeof (struct aac_fib_header))) return (EINVAL); if ((acp = kmem_zalloc(sizeof (struct aac_cmd) + usr_fib_size + \ sizeof (struct aac_fib_header), KM_NOSLEEP)) == NULL) return (ENOMEM); acp->fibp = (struct aac_fib *)(acp + 1); fibp = acp->fibp; srb = (struct aac_srb *)fibp->data; /* Copy in srb */ if (ddi_copyin((void *)arg, srb, usr_fib_size, mode) != 0) { rval = EFAULT; goto finish; } srb_sgcount = srb->sg.SgCount; /* No endianness conversion needed */ if (srb_sgcount == 0) goto send_fib; /* Check FIB size */ if (usr_fib_size == (sizeof (struct aac_srb) + \ srb_sgcount * sizeof (struct aac_sg_entry64) - \ sizeof (struct aac_sg_entry))) { sg64 = 1; } else if (usr_fib_size == (sizeof (struct aac_srb) + \ (srb_sgcount - 1) * sizeof (struct aac_sg_entry))) { sg64 = 0; } else { rval = EINVAL; goto finish; } /* Read user SG table */ if ((usgt = kmem_zalloc(sizeof (struct aac_umem_sge) * srb_sgcount, KM_NOSLEEP)) == NULL) { rval = ENOMEM; goto finish; } for (usge = usgt; usge < &usgt[srb_sgcount]; usge++) { if (sg64) { struct aac_sg_entry64 *sg64p = (struct aac_sg_entry64 *)srb->sg.SgEntry; usge->bcount = sg64p->SgByteCount; usge->addr = (caddr_t) #ifndef _LP64 (uint32_t) #endif sg64p->SgAddress; } else { struct aac_sg_entry *sgp = srb->sg.SgEntry; usge->bcount = sgp->SgByteCount; usge->addr = (caddr_t) #ifdef _LP64 (uint64_t) #endif sgp->SgAddress; } acp->bcount += usge->bcount; if (usge->addr < addrlo) addrlo = usge->addr; if ((usge->addr + usge->bcount) > addrhi) addrhi = usge->addr + usge->bcount; } if (acp->bcount > softs->buf_dma_attr.dma_attr_maxxfer) { AACDB_PRINT(softs, CE_NOTE, "large srb xfer size received %d\n", acp->bcount); rval = EINVAL; goto finish; } /* Lock user buffers */ if (srb->flags & SRB_DataIn) { umem_flags |= DDI_UMEMLOCK_READ; direct |= B_READ; } if (srb->flags & SRB_DataOut) { umem_flags |= DDI_UMEMLOCK_WRITE; direct |= B_WRITE; } addrlo = (caddr_t)((uintptr_t)addrlo & (uintptr_t)PAGEMASK); rval = ddi_umem_lock(addrlo, (((size_t)addrhi + PAGEOFFSET) & \ PAGEMASK) - (size_t)addrlo, umem_flags, &cookie); if (rval != 0) { AACDB_PRINT(softs, CE_NOTE, "ddi_umem_lock failed: %d", rval); goto finish; } locked = 1; /* Allocate DMA for user buffers */ for (usge = usgt; usge < &usgt[srb_sgcount]; usge++) { struct buf *bp; bp = ddi_umem_iosetup(cookie, usge->addr - addrlo, usge->bcount, direct, dev, 0, NULL, DDI_UMEM_SLEEP); if (bp == NULL) { AACDB_PRINT(softs, CE_NOTE, "ddi_umem_iosetup failed"); rval = EFAULT; goto finish; } if (aac_cmd_dma_alloc(softs, &usge->acp, bp, 0, NULL_FUNC, 0) != AACOK) { rval = EFAULT; goto finish; } acp->left_cookien += usge->acp.left_cookien; if (acp->left_cookien > softs->aac_sg_tablesize) { AACDB_PRINT(softs, CE_NOTE, "large cookiec received %d", acp->left_cookien); rval = EINVAL; goto finish; } } /* Construct aac cmd SG table */ if ((sge = kmem_zalloc(sizeof (struct aac_sge) * acp->left_cookien, KM_NOSLEEP)) == NULL) { rval = ENOMEM; goto finish; } acp->sgt = sge; for (usge = usgt; usge < &usgt[srb_sgcount]; usge++) { for (sge0 = usge->acp.sgt; sge0 < &usge->acp.sgt[usge->acp.left_cookien]; sge0++, sge++) *sge = *sge0; } send_fib: acp->cmdlen = srb->cdb_size; acp->timeout = srb->timeout; /* Send FIB command */ AACDB_PRINT_FIB(softs, fibp); acp->aac_cmd_fib = softs->aac_cmd_fib_scsi; if ((rval = aac_send_fib(softs, acp)) != 0) goto finish; /* Status struct */ if (ddi_copyout((struct aac_srb_reply *)fibp->data, ((uint8_t *)arg + usr_fib_size), sizeof (struct aac_srb_reply), mode) != 0) { rval = EFAULT; goto finish; } rval = 0; finish: if (acp->sgt) kmem_free(acp->sgt, sizeof (struct aac_sge) * \ acp->left_cookien); if (usgt) { for (usge = usgt; usge < &usgt[srb_sgcount]; usge++) { if (usge->acp.sgt) kmem_free(usge->acp.sgt, sizeof (struct aac_sge) * \ usge->acp.left_cookien); aac_free_dmamap(&usge->acp); if (usge->acp.bp) freerbuf(usge->acp.bp); } kmem_free(usgt, sizeof (struct aac_umem_sge) * srb_sgcount); } if (locked) ddi_umem_unlock(cookie); kmem_free(acp, sizeof (struct aac_cmd) + usr_fib_size + \ sizeof (struct aac_fib_header)); return (rval); } /*ARGSUSED*/ static int aac_get_pci_info(struct aac_softstate *softs, intptr_t arg, int mode) { union aac_pci_info_align un; struct aac_pci_info *resp = &un.d; DBCALLED(softs, 2); resp->bus = 0; resp->slot = 0; if (ddi_copyout(resp, (void *)arg, sizeof (struct aac_pci_info), mode) != 0) return (EFAULT); return (0); } static int aac_query_disk(struct aac_softstate *softs, intptr_t arg, int mode) { union aac_query_disk_align un; struct aac_query_disk *qdisk = &un.d; struct aac_container *dvp; DBCALLED(softs, 2); if (ddi_copyin((void *)arg, qdisk, sizeof (*qdisk), mode) != 0) return (EFAULT); if (qdisk->container_no == -1) { qdisk->container_no = qdisk->target * 16 + qdisk->lun; } else if (qdisk->bus == -1 && qdisk->target == -1 && qdisk->lun == -1) { if (qdisk->container_no >= AAC_MAX_CONTAINERS) return (EINVAL); qdisk->bus = 0; qdisk->target = (qdisk->container_no & 0xf); qdisk->lun = (qdisk->container_no >> 4); } else { return (EINVAL); } mutex_enter(&softs->io_lock); dvp = &softs->containers[qdisk->container_no]; qdisk->valid = dvp->valid; qdisk->locked = dvp->locked; qdisk->deleted = dvp->deleted; mutex_exit(&softs->io_lock); if (ddi_copyout(qdisk, (void *)arg, sizeof (*qdisk), mode) != 0) return (EFAULT); return (0); } static int aac_delete_disk(struct aac_softstate *softs, intptr_t arg, int mode) { union aac_delete_disk_align un; struct aac_delete_disk *ddisk = &un.d; struct aac_container *dvp; int rval = 0; DBCALLED(softs, 2); if (ddi_copyin((void *)arg, ddisk, sizeof (*ddisk), mode) != 0) return (EFAULT); if (ddisk->container_no >= AAC_MAX_CONTAINERS) return (EINVAL); mutex_enter(&softs->io_lock); dvp = &softs->containers[ddisk->container_no]; /* * We don't trust the userland to tell us when to delete * a container, rather we rely on an AIF coming from the * controller. */ if (dvp->valid) { if (dvp->locked) rval = EBUSY; } mutex_exit(&softs->io_lock); return (rval); } /* * The following function comes from Adaptec to support creation of arrays * bigger than 2TB. */ static int aac_supported_features(struct aac_softstate *softs, intptr_t arg, int mode) { union aac_features_align un; struct aac_features *f = &un.d; DBCALLED(softs, 2); if (ddi_copyin((void *)arg, f, sizeof (*f), mode) != 0) return (EFAULT); /* * When the management driver receives FSACTL_GET_FEATURES ioctl with * ALL zero in the featuresState, the driver will return the current * state of all the supported features, the data field will not be * valid. * When the management driver receives FSACTL_GET_FEATURES ioctl with * a specific bit set in the featuresState, the driver will return the * current state of this specific feature and whatever data that are * associated with the feature in the data field or perform whatever * action needed indicates in the data field. */ if (f->feat.fValue == 0) { f->feat.fBits.largeLBA = (softs->flags & AAC_FLAGS_LBA_64BIT) ? 1 : 0; /* TODO: In the future, add other features state here as well */ } else { if (f->feat.fBits.largeLBA) f->feat.fBits.largeLBA = (softs->flags & AAC_FLAGS_LBA_64BIT) ? 1 : 0; /* TODO: Add other features state and data in the future */ } if (ddi_copyout(f, (void *)arg, sizeof (*f), mode) != 0) return (EFAULT); return (0); }