static int hrz_open()

in horizon.c [2093:2474]


static int hrz_open (struct atm_vcc *atm_vcc)
{
  int error;
  u16 channel;
  
  struct atm_qos * qos;
  struct atm_trafprm * txtp;
  struct atm_trafprm * rxtp;
  
  hrz_dev * dev = HRZ_DEV(atm_vcc->dev);
  hrz_vcc vcc;
  hrz_vcc * vccp; // allocated late
  short vpi = atm_vcc->vpi;
  int vci = atm_vcc->vci;
  PRINTD (DBG_FLOW|DBG_VCC, "hrz_open %x %x", vpi, vci);
  
#ifdef ATM_VPI_UNSPEC
  // UNSPEC is deprecated, remove this code eventually
  if (vpi == ATM_VPI_UNSPEC || vci == ATM_VCI_UNSPEC) {
    PRINTK (KERN_WARNING, "rejecting open with unspecified VPI/VCI (deprecated)");
    return -EINVAL;
  }
#endif
  
  error = vpivci_to_channel (&channel, vpi, vci);
  if (error) {
    PRINTD (DBG_WARN|DBG_VCC, "VPI/VCI out of range: %hd/%d", vpi, vci);
    return error;
  }
  
  vcc.channel = channel;
  // max speed for the moment
  vcc.tx_rate = 0x0;
  
  qos = &atm_vcc->qos;
  
  // check AAL and remember it
  switch (qos->aal) {
    case ATM_AAL0:
      // we would if it were 48 bytes and not 52!
      PRINTD (DBG_QOS|DBG_VCC, "AAL0");
      vcc.aal = aal0;
      break;
    case ATM_AAL34:
      // we would if I knew how do the SAR!
      PRINTD (DBG_QOS|DBG_VCC, "AAL3/4");
      vcc.aal = aal34;
      break;
    case ATM_AAL5:
      PRINTD (DBG_QOS|DBG_VCC, "AAL5");
      vcc.aal = aal5;
      break;
    default:
      PRINTD (DBG_QOS|DBG_VCC, "Bad AAL!");
      return -EINVAL;
  }
  
  // TX traffic parameters
  
  // there are two, interrelated problems here: 1. the reservation of
  // PCR is not a binary choice, we are given bounds and/or a
  // desirable value; 2. the device is only capable of certain values,
  // most of which are not integers. It is almost certainly acceptable
  // to be off by a maximum of 1 to 10 cps.
  
  // Pragmatic choice: always store an integral PCR as that which has
  // been allocated, even if we allocate a little (or a lot) less,
  // after rounding. The actual allocation depends on what we can
  // manage with our rate selection algorithm. The rate selection
  // algorithm is given an integral PCR and a tolerance and told
  // whether it should round the value up or down if the tolerance is
  // exceeded; it returns: a) the actual rate selected (rounded up to
  // the nearest integer), b) a bit pattern to feed to the timer
  // register, and c) a failure value if no applicable rate exists.
  
  // Part of the job is done by atm_pcr_goal which gives us a PCR
  // specification which says: EITHER grab the maximum available PCR
  // (and perhaps a lower bound which we must not pass), OR grab this
  // amount, rounding down if you have to (and perhaps a lower bound
  // which we must not pass) OR grab this amount, rounding up if you
  // have to (and perhaps an upper bound which we must not pass). If any
  // bounds ARE passed we fail. Note that rounding is only rounding to
  // match device limitations, we do not round down to satisfy
  // bandwidth availability even if this would not violate any given
  // lower bound.
  
  // Note: telephony = 64kb/s = 48 byte cell payload @ 500/3 cells/s
  // (say) so this is not even a binary fixpoint cell rate (but this
  // device can do it). To avoid this sort of hassle we use a
  // tolerance parameter (currently fixed at 10 cps).
  
  PRINTD (DBG_QOS, "TX:");
  
  txtp = &qos->txtp;
  
  // set up defaults for no traffic
  vcc.tx_rate = 0;
  // who knows what would actually happen if you try and send on this?
  vcc.tx_xbr_bits = IDLE_RATE_TYPE;
  vcc.tx_pcr_bits = CLOCK_DISABLE;
#if 0
  vcc.tx_scr_bits = CLOCK_DISABLE;
  vcc.tx_bucket_bits = 0;
#endif
  
  if (txtp->traffic_class != ATM_NONE) {
    error = check_max_sdu (vcc.aal, txtp, max_tx_size);
    if (error) {
      PRINTD (DBG_QOS, "TX max_sdu check failed");
      return error;
    }
    
    switch (txtp->traffic_class) {
      case ATM_UBR: {
	// we take "the PCR" as a rate-cap
	// not reserved
	vcc.tx_rate = 0;
	make_rate (dev, 1<<30, round_nearest, &vcc.tx_pcr_bits, NULL);
	vcc.tx_xbr_bits = ABR_RATE_TYPE;
	break;
      }
#if 0
      case ATM_ABR: {
	// reserve min, allow up to max
	vcc.tx_rate = 0; // ?
	make_rate (dev, 1<<30, round_nearest, &vcc.tx_pcr_bits, 0);
	vcc.tx_xbr_bits = ABR_RATE_TYPE;
	break;
      }
#endif
      case ATM_CBR: {
	int pcr = atm_pcr_goal (txtp);
	rounding r;
	if (!pcr) {
	  // down vs. up, remaining bandwidth vs. unlimited bandwidth!!
	  // should really have: once someone gets unlimited bandwidth
	  // that no more non-UBR channels can be opened until the
	  // unlimited one closes?? For the moment, round_down means
	  // greedy people actually get something and not nothing
	  r = round_down;
	  // slight race (no locking) here so we may get -EAGAIN
	  // later; the greedy bastards would deserve it :)
	  PRINTD (DBG_QOS, "snatching all remaining TX bandwidth");
	  pcr = dev->tx_avail;
	} else if (pcr < 0) {
	  r = round_down;
	  pcr = -pcr;
	} else {
	  r = round_up;
	}
	error = make_rate_with_tolerance (dev, pcr, r, 10,
					  &vcc.tx_pcr_bits, &vcc.tx_rate);
	if (error) {
	  PRINTD (DBG_QOS, "could not make rate from TX PCR");
	  return error;
	}
	// not really clear what further checking is needed
	error = atm_pcr_check (txtp, vcc.tx_rate);
	if (error) {
	  PRINTD (DBG_QOS, "TX PCR failed consistency check");
	  return error;
	}
	vcc.tx_xbr_bits = CBR_RATE_TYPE;
	break;
      }
#if 0
      case ATM_VBR: {
	int pcr = atm_pcr_goal (txtp);
	// int scr = atm_scr_goal (txtp);
	int scr = pcr/2; // just for fun
	unsigned int mbs = 60; // just for fun
	rounding pr;
	rounding sr;
	unsigned int bucket;
	if (!pcr) {
	  pr = round_nearest;
	  pcr = 1<<30;
	} else if (pcr < 0) {
	  pr = round_down;
	  pcr = -pcr;
	} else {
	  pr = round_up;
	}
	error = make_rate_with_tolerance (dev, pcr, pr, 10,
					  &vcc.tx_pcr_bits, 0);
	if (!scr) {
	  // see comments for PCR with CBR above
	  sr = round_down;
	  // slight race (no locking) here so we may get -EAGAIN
	  // later; the greedy bastards would deserve it :)
	  PRINTD (DBG_QOS, "snatching all remaining TX bandwidth");
	  scr = dev->tx_avail;
	} else if (scr < 0) {
	  sr = round_down;
	  scr = -scr;
	} else {
	  sr = round_up;
	}
	error = make_rate_with_tolerance (dev, scr, sr, 10,
					  &vcc.tx_scr_bits, &vcc.tx_rate);
	if (error) {
	  PRINTD (DBG_QOS, "could not make rate from TX SCR");
	  return error;
	}
	// not really clear what further checking is needed
	// error = atm_scr_check (txtp, vcc.tx_rate);
	if (error) {
	  PRINTD (DBG_QOS, "TX SCR failed consistency check");
	  return error;
	}
	// bucket calculations (from a piece of paper...) cell bucket
	// capacity must be largest integer smaller than m(p-s)/p + 1
	// where m = max burst size, p = pcr, s = scr
	bucket = mbs*(pcr-scr)/pcr;
	if (bucket*pcr != mbs*(pcr-scr))
	  bucket += 1;
	if (bucket > BUCKET_MAX_SIZE) {
	  PRINTD (DBG_QOS, "shrinking bucket from %u to %u",
		  bucket, BUCKET_MAX_SIZE);
	  bucket = BUCKET_MAX_SIZE;
	}
	vcc.tx_xbr_bits = VBR_RATE_TYPE;
	vcc.tx_bucket_bits = bucket;
	break;
      }
#endif
      default: {
	PRINTD (DBG_QOS, "unsupported TX traffic class");
	return -EINVAL;
      }
    }
  }
  
  // RX traffic parameters
  
  PRINTD (DBG_QOS, "RX:");
  
  rxtp = &qos->rxtp;
  
  // set up defaults for no traffic
  vcc.rx_rate = 0;
  
  if (rxtp->traffic_class != ATM_NONE) {
    error = check_max_sdu (vcc.aal, rxtp, max_rx_size);
    if (error) {
      PRINTD (DBG_QOS, "RX max_sdu check failed");
      return error;
    }
    switch (rxtp->traffic_class) {
      case ATM_UBR: {
	// not reserved
	break;
      }
#if 0
      case ATM_ABR: {
	// reserve min
	vcc.rx_rate = 0; // ?
	break;
      }
#endif
      case ATM_CBR: {
	int pcr = atm_pcr_goal (rxtp);
	if (!pcr) {
	  // slight race (no locking) here so we may get -EAGAIN
	  // later; the greedy bastards would deserve it :)
	  PRINTD (DBG_QOS, "snatching all remaining RX bandwidth");
	  pcr = dev->rx_avail;
	} else if (pcr < 0) {
	  pcr = -pcr;
	}
	vcc.rx_rate = pcr;
	// not really clear what further checking is needed
	error = atm_pcr_check (rxtp, vcc.rx_rate);
	if (error) {
	  PRINTD (DBG_QOS, "RX PCR failed consistency check");
	  return error;
	}
	break;
      }
#if 0
      case ATM_VBR: {
	// int scr = atm_scr_goal (rxtp);
	int scr = 1<<16; // just for fun
	if (!scr) {
	  // slight race (no locking) here so we may get -EAGAIN
	  // later; the greedy bastards would deserve it :)
	  PRINTD (DBG_QOS, "snatching all remaining RX bandwidth");
	  scr = dev->rx_avail;
	} else if (scr < 0) {
	  scr = -scr;
	}
	vcc.rx_rate = scr;
	// not really clear what further checking is needed
	// error = atm_scr_check (rxtp, vcc.rx_rate);
	if (error) {
	  PRINTD (DBG_QOS, "RX SCR failed consistency check");
	  return error;
	}
	break;
      }
#endif
      default: {
	PRINTD (DBG_QOS, "unsupported RX traffic class");
	return -EINVAL;
      }
    }
  }
  
  
  // late abort useful for diagnostics
  if (vcc.aal != aal5) {
    PRINTD (DBG_QOS, "AAL not supported");
    return -EINVAL;
  }
  
  // get space for our vcc stuff and copy parameters into it
  vccp = kmalloc (sizeof(hrz_vcc), GFP_KERNEL);
  if (!vccp) {
    PRINTK (KERN_ERR, "out of memory!");
    return -ENOMEM;
  }
  *vccp = vcc;
  
  // clear error and grab cell rate resource lock
  error = 0;
  spin_lock (&dev->rate_lock);
  
  if (vcc.tx_rate > dev->tx_avail) {
    PRINTD (DBG_QOS, "not enough TX PCR left");
    error = -EAGAIN;
  }
  
  if (vcc.rx_rate > dev->rx_avail) {
    PRINTD (DBG_QOS, "not enough RX PCR left");
    error = -EAGAIN;
  }
  
  if (!error) {
    // really consume cell rates
    dev->tx_avail -= vcc.tx_rate;
    dev->rx_avail -= vcc.rx_rate;
    PRINTD (DBG_QOS|DBG_VCC, "reserving %u TX PCR and %u RX PCR",
	    vcc.tx_rate, vcc.rx_rate);
  }
  
  // release lock and exit on error
  spin_unlock (&dev->rate_lock);
  if (error) {
    PRINTD (DBG_QOS|DBG_VCC, "insufficient cell rate resources");
    kfree (vccp);
    return error;
  }
  
  // this is "immediately before allocating the connection identifier
  // in hardware" - so long as the next call does not fail :)
  set_bit(ATM_VF_ADDR,&atm_vcc->flags);
  
  // any errors here are very serious and should never occur
  
  if (rxtp->traffic_class != ATM_NONE) {
    if (dev->rxer[channel]) {
      PRINTD (DBG_ERR|DBG_VCC, "VC already open for RX");
      error = -EBUSY;
    }
    if (!error)
      error = hrz_open_rx (dev, channel);
    if (error) {
      kfree (vccp);
      return error;
    }
    // this link allows RX frames through
    dev->rxer[channel] = atm_vcc;
  }
  
  // success, set elements of atm_vcc
  atm_vcc->dev_data = (void *) vccp;
  
  // indicate readiness
  set_bit(ATM_VF_READY,&atm_vcc->flags);
  
  return 0;
}