346 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			346 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* libbdev - IPC and recovery functions */
 | |
| 
 | |
| #include <minix/drivers.h>
 | |
| #include <minix/bdev.h>
 | |
| #include <assert.h>
 | |
| 
 | |
| #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.BDEV_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.BDEV_ID = NO_ID;
 | |
| 
 | |
|   r = 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.BDEV_ID != NO_ID) {
 | |
| 	printf("bdev: driver (%d) sent invalid ID (%ld)\n", endpt, m.BDEV_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.BDEV_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.BDEV_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->BDEV_ID;
 | |
| 
 | |
|   if ((call = bdev_call_get(id)) == NULL) {
 | |
| 	printf("bdev: driver (%d) replied to unknown request (%ld)\n",
 | |
| 		m->m_source, m->BDEV_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->BDEV_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->BDEV_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;
 | |
| }
 | 
