/* libbdev - IPC and recovery functions */ #include #include #include #include "const.h" #include "type.h" #include "proto.h" static void bdev_cancel(dev_t dev) { /* Recovering the driver for the given device has failed repeatedly. Mark it as * permanently unusable, and clean up any associated calls and resources. */ bdev_call_t *call, *next; printf("bdev: giving up on major %d\n", major(dev)); /* Cancel all pending asynchronous requests. */ call = NULL; while ((call = bdev_call_iter_maj(dev, call, &next)) != NULL) bdev_callback_asyn(call, EDEADSRCDST); /* Mark the driver as unusable. */ bdev_driver_clear(dev); } static int bdev_recover(dev_t dev, int update_endpt) { /* The IPC subsystem has signaled an error communicating to the driver * associated with the given device. Try to recover. If 'update_endpt' is set, * we need to find the new endpoint of the driver first. Return TRUE iff * recovery has been successful. */ bdev_call_t *call, *next; endpoint_t endpt; int r, active, nr_tries; /* Only print output if there is something to recover. Some drivers may be * shut down and later restarted legitimately, and if they were not in use * while that happened, there is no need to flood the console with messages. */ active = bdev_minor_is_open(dev) || bdev_call_iter_maj(dev, NULL, &next); if (active) printf("bdev: recovering from a driver restart on major %d\n", major(dev)); for (nr_tries = 0; nr_tries < RECOVER_TRIES; nr_tries++) { /* First update the endpoint, if necessary. */ if (update_endpt) (void) bdev_driver_update(dev); if ((endpt = bdev_driver_get(dev)) == NONE) break; /* If anything goes wrong, update the endpoint again next time. */ update_endpt = TRUE; /* Reopen all minor devices on the new driver. */ if ((r = bdev_minor_reopen(dev)) != OK) { /* If the driver died again, we may give it another try. */ if (r == EDEADSRCDST) continue; /* If another error occurred, we cannot continue using the * driver as is, but we also cannot force it to restart. */ break; } /* Resend all asynchronous requests. */ call = NULL; while ((call = bdev_call_iter_maj(dev, call, &next)) != NULL) { /* It is not strictly necessary that we manage to reissue all * asynchronous requests successfully. We can fail them on an * individual basis here, without affecting the overall * recovery. Note that we will never get new IPC failures here. */ if ((r = bdev_restart_asyn(call)) != OK) bdev_callback_asyn(call, r); } /* Recovery seems successful. We can now reissue the current * synchronous request (if any), and continue normal operation. */ if (active) printf("bdev: recovery successful, new driver at %d\n", endpt); return TRUE; } /* Recovery failed repeatedly. Give up on this driver. */ bdev_cancel(dev); return FALSE; } void bdev_update(dev_t dev, char *label) { /* Set the endpoint for a driver. Perform recovery if necessary. */ endpoint_t endpt, old_endpt; old_endpt = bdev_driver_get(dev); endpt = bdev_driver_set(dev, label); /* If updating the driver causes an endpoint change, we need to perform * recovery, but not update the endpoint yet again. */ if (old_endpt != NONE && old_endpt != endpt) bdev_recover(dev, FALSE /*update_endpt*/); } int bdev_senda(dev_t dev, const message *m_orig, bdev_id_t id) { /* Send an asynchronous request for the given device. This function will never * get any new IPC errors sending to the driver. If sending an asynchronous * request fails, we will find out through other ways later. */ endpoint_t endpt; message m; int r; /* If we have no usable driver endpoint, fail instantly. */ if ((endpt = bdev_driver_get(dev)) == NONE) return EDEADSRCDST; m = *m_orig; m.m_lbdev_lblockdriver_msg.id = id; r = asynsend(endpt, &m); if (r != OK) printf("bdev: asynsend to driver (%d) failed (%d)\n", endpt, r); return r; } int bdev_sendrec(dev_t dev, const message *m_orig) { /* Send a synchronous request for the given device, and wait for the reply. * Return ERESTART if the caller should try to reissue the request. */ endpoint_t endpt; message m; int r; /* If we have no usable driver endpoint, fail instantly. */ if ((endpt = bdev_driver_get(dev)) == NONE) return EDEADSRCDST; /* Send the request and block until we receive a reply. */ m = *m_orig; m.m_lbdev_lblockdriver_msg.id = NO_ID; r = ipc_sendrec(endpt, &m); /* If communication failed, the driver has died. We assume it will be * restarted soon after, so we attempt recovery. Upon success, we let the * caller reissue the synchronous request. */ if (r == EDEADSRCDST) { if (!bdev_recover(dev, TRUE /*update_endpt*/)) return EDEADSRCDST; return ERESTART; } if (r != OK) { printf("bdev: IPC to driver (%d) failed (%d)\n", endpt, r); return r; } if (m.m_type != BDEV_REPLY) { printf("bdev: driver (%d) sent weird response (%d)\n", endpt, m.m_type); return EINVAL; } /* The protocol contract states that no asynchronous reply can satisfy a * synchronous SENDREC call, so we can never get an asynchronous reply here. */ if (m.m_lblockdriver_lbdev_reply.id != NO_ID) { printf("bdev: driver (%d) sent invalid ID (%d)\n", endpt, m.m_lblockdriver_lbdev_reply.id); return EINVAL; } /* Unless the caller is misusing libbdev, we will only get ERESTART if we * have managed to resend a raw block I/O request to the driver after a * restart, but before VFS has had a chance to reopen the associated device * first. This is highly exceptional, and hard to deal with correctly. We * take the easiest route: sleep for a while so that VFS can reopen the * device, and then resend the request. If the call keeps failing, the caller * will eventually give up. */ if (m.m_lblockdriver_lbdev_reply.status == ERESTART) { printf("bdev: got ERESTART from driver (%d), sleeping for reopen\n", endpt); micro_delay(1000); return ERESTART; } /* Return the result of our request. */ return m.m_lblockdriver_lbdev_reply.status; } static int bdev_receive(dev_t dev, message *m) { /* Receive one valid message. */ endpoint_t endpt; int r, nr_tries = 0; for (;;) { /* Retrieve and check the driver endpoint on every try, as it will * change with each driver restart. */ if ((endpt = bdev_driver_get(dev)) == NONE) return EDEADSRCDST; r = sef_receive(endpt, m); if (r == EDEADSRCDST) { /* If we reached the maximum number of retries, give up. */ if (++nr_tries == DRIVER_TRIES) break; /* Attempt recovery. If successful, all asynchronous requests * will have been resent, and we can retry receiving a reply. */ if (!bdev_recover(dev, TRUE /*update_endpt*/)) return EDEADSRCDST; continue; } if (r != OK) { printf("bdev: IPC to driver (%d) failed (%d)\n", endpt, r); return r; } if (m->m_type != BDEV_REPLY) { printf("bdev: driver (%d) sent weird response (%d)\n", endpt, m->m_type); return EINVAL; } /* The caller is responsible for checking the ID and status. */ return OK; } /* All tries failed, even though all recovery attempts succeeded. In this * case, we let the caller recheck whether it wants to keep calling us, * returning ERESTART to indicate we can be called again but did not actually * receive a message. */ return ERESTART; } void bdev_reply_asyn(message *m) { /* A reply has come in from a disk driver. */ bdev_call_t *call; endpoint_t endpt; bdev_id_t id; int r; /* This is a requirement for the caller. */ assert(m->m_type == BDEV_REPLY); /* Get the corresponding asynchronous call structure. */ id = m->m_lblockdriver_lbdev_reply.id; if ((call = bdev_call_get(id)) == NULL) { printf("bdev: driver (%d) replied to unknown request (%d)\n", m->m_source, m->m_lblockdriver_lbdev_reply.id); return; } /* Make sure the reply was sent from the right endpoint. */ endpt = bdev_driver_get(call->dev); if (m->m_source != endpt) { /* If the endpoint is NONE, this may be a stray reply. */ if (endpt != NONE) printf("bdev: driver (%d) replied to request not sent to it\n", m->m_source); return; } /* See the ERESTART comment in bdev_sendrec(). */ if (m->m_lblockdriver_lbdev_reply.status == ERESTART) { printf("bdev: got ERESTART from driver (%d), sleeping for reopen\n", endpt); micro_delay(1000); if ((r = bdev_restart_asyn(call)) != OK) bdev_callback_asyn(call, r); return; } bdev_callback_asyn(call, m->m_lblockdriver_lbdev_reply.status); } int bdev_wait_asyn(bdev_id_t id) { /* Wait for an asynchronous request to complete. */ bdev_call_t *call; dev_t dev; message m; int r; if ((call = bdev_call_get(id)) == NULL) return ENOENT; dev = call->dev; do { if ((r = bdev_receive(dev, &m)) != OK && r != ERESTART) return r; /* Processing the reply will free up the call structure as a side * effect. If we repeatedly get ERESTART, we will repeatedly resend the * asynchronous request, which will then eventually hit the retry limit * and we will break out of the loop. */ if (r == OK) bdev_reply_asyn(&m); } while (bdev_call_get(id) != NULL); return OK; }