int ibmphp_check_resource()

in hotplug/ibmphp_res.c [952:1332]


int ibmphp_check_resource(struct resource_node *res, u8 bridge)
{
	struct bus_node *bus_cur;
	struct range_node *range = NULL;
	struct resource_node *res_prev;
	struct resource_node *res_cur = NULL;
	u32 len_cur = 0, start_cur = 0, len_tmp = 0;
	int noranges = 0;
	u32 tmp_start;		/* this is to make sure start address is divisible by the length needed */
	u32 tmp_divide;
	u8 flag = 0;

	if (!res)
		return -EINVAL;

	if (bridge) {
		/* The rules for bridges are different, 4K divisible for IO, 1M for (pf)mem*/
		if (res->type == IO)
			tmp_divide = IOBRIDGE;
		else
			tmp_divide = MEMBRIDGE;
	} else
		tmp_divide = res->len;

	bus_cur = find_bus_wprev(res->busno, NULL, 0);

	if (!bus_cur) {
		/* didn't find a bus, something's wrong!!! */
		debug("no bus in the system, either pci_dev's wrong or allocation failed\n");
		return -EINVAL;
	}

	debug("%s - enter\n", __func__);
	debug("bus_cur->busno is %d\n", bus_cur->busno);

	/* This is a quick fix to not mess up with the code very much.  i.e.,
	 * 2000-2fff, len = 1000, but when we compare, we need it to be fff */
	res->len -= 1;

	switch (res->type) {
		case IO:
			res_cur = bus_cur->firstIO;
			noranges = bus_cur->noIORanges;
			break;
		case MEM:
			res_cur = bus_cur->firstMem;
			noranges = bus_cur->noMemRanges;
			break;
		case PFMEM:
			res_cur = bus_cur->firstPFMem;
			noranges = bus_cur->noPFMemRanges;
			break;
		default:
			err("wrong type of resource to check\n");
			return -EINVAL;
	}
	res_prev = NULL;

	while (res_cur) {
		range = find_range(bus_cur, res_cur);
		debug("%s - rangeno = %d\n", __func__, res_cur->rangeno);

		if (!range) {
			err("no range for the device exists... bailing out...\n");
			return -EINVAL;
		}

		/* found our range */
		if (!res_prev) {
			/* first time in the loop */
			len_tmp = res_cur->start - 1 - range->start;

			if ((res_cur->start != range->start) && (len_tmp >= res->len)) {
				debug("len_tmp = %x\n", len_tmp);

				if ((len_tmp < len_cur) || (len_cur == 0)) {

					if ((range->start % tmp_divide) == 0) {
						/* just perfect, starting address is divisible by length */
						flag = 1;
						len_cur = len_tmp;
						start_cur = range->start;
					} else {
						/* Needs adjusting */
						tmp_start = range->start;
						flag = 0;

						while ((len_tmp = res_cur->start - 1 - tmp_start) >= res->len) {
							if ((tmp_start % tmp_divide) == 0) {
								flag = 1;
								len_cur = len_tmp;
								start_cur = tmp_start;
								break;
							}
							tmp_start += tmp_divide - tmp_start % tmp_divide;
							if (tmp_start >= res_cur->start - 1)
								break;
						}
					}

					if (flag && len_cur == res->len) {
						debug("but we are not here, right?\n");
						res->start = start_cur;
						res->len += 1; /* To restore the balance */
						res->end = res->start + res->len - 1;
						return 0;
					}
				}
			}
		}
		if (!res_cur->next) {
			/* last device on the range */
			len_tmp = range->end - (res_cur->end + 1);

			if ((range->end != res_cur->end) && (len_tmp >= res->len)) {
				debug("len_tmp = %x\n", len_tmp);
				if ((len_tmp < len_cur) || (len_cur == 0)) {

					if (((res_cur->end + 1) % tmp_divide) == 0) {
						/* just perfect, starting address is divisible by length */
						flag = 1;
						len_cur = len_tmp;
						start_cur = res_cur->end + 1;
					} else {
						/* Needs adjusting */
						tmp_start = res_cur->end + 1;
						flag = 0;

						while ((len_tmp = range->end - tmp_start) >= res->len) {
							if ((tmp_start % tmp_divide) == 0) {
								flag = 1;
								len_cur = len_tmp;
								start_cur = tmp_start;
								break;
							}
							tmp_start += tmp_divide - tmp_start % tmp_divide;
							if (tmp_start >= range->end)
								break;
						}
					}
					if (flag && len_cur == res->len) {
						res->start = start_cur;
						res->len += 1; /* To restore the balance */
						res->end = res->start + res->len - 1;
						return 0;
					}
				}
			}
		}

		if (res_prev) {
			if (res_prev->rangeno != res_cur->rangeno) {
				/* 1st device on this range */
				len_tmp = res_cur->start - 1 - range->start;

				if ((res_cur->start != range->start) &&	(len_tmp >= res->len)) {
					if ((len_tmp < len_cur) || (len_cur == 0)) {
						if ((range->start % tmp_divide) == 0) {
							/* just perfect, starting address is divisible by length */
							flag = 1;
							len_cur = len_tmp;
							start_cur = range->start;
						} else {
							/* Needs adjusting */
							tmp_start = range->start;
							flag = 0;

							while ((len_tmp = res_cur->start - 1 - tmp_start) >= res->len) {
								if ((tmp_start % tmp_divide) == 0) {
									flag = 1;
									len_cur = len_tmp;
									start_cur = tmp_start;
									break;
								}
								tmp_start += tmp_divide - tmp_start % tmp_divide;
								if (tmp_start >= res_cur->start - 1)
									break;
							}
						}

						if (flag && len_cur == res->len) {
							res->start = start_cur;
							res->len += 1; /* To restore the balance */
							res->end = res->start + res->len - 1;
							return 0;
						}
					}
				}
			} else {
				/* in the same range */
				len_tmp = res_cur->start - 1 - res_prev->end - 1;

				if (len_tmp >= res->len) {
					if ((len_tmp < len_cur) || (len_cur == 0)) {
						if (((res_prev->end + 1) % tmp_divide) == 0) {
							/* just perfect, starting address's divisible by length */
							flag = 1;
							len_cur = len_tmp;
							start_cur = res_prev->end + 1;
						} else {
							/* Needs adjusting */
							tmp_start = res_prev->end + 1;
							flag = 0;

							while ((len_tmp = res_cur->start - 1 - tmp_start) >= res->len) {
								if ((tmp_start % tmp_divide) == 0) {
									flag = 1;
									len_cur = len_tmp;
									start_cur = tmp_start;
									break;
								}
								tmp_start += tmp_divide - tmp_start % tmp_divide;
								if (tmp_start >= res_cur->start - 1)
									break;
							}
						}

						if (flag && len_cur == res->len) {
							res->start = start_cur;
							res->len += 1; /* To restore the balance */
							res->end = res->start + res->len - 1;
							return 0;
						}
					}
				}
			}
		}
		/* end if (res_prev) */
		res_prev = res_cur;
		if (res_cur->next)
			res_cur = res_cur->next;
		else
			res_cur = res_cur->nextRange;
	}	/* end of while */


	if (!res_prev) {
		/* 1st device ever */
		/* need to find appropriate range */
		switch (res->type) {
			case IO:
				range = bus_cur->rangeIO;
				break;
			case MEM:
				range = bus_cur->rangeMem;
				break;
			case PFMEM:
				range = bus_cur->rangePFMem;
				break;
		}
		while (range) {
			len_tmp = range->end - range->start;

			if (len_tmp >= res->len) {
				if ((len_tmp < len_cur) || (len_cur == 0)) {
					if ((range->start % tmp_divide) == 0) {
						/* just perfect, starting address's divisible by length */
						flag = 1;
						len_cur = len_tmp;
						start_cur = range->start;
					} else {
						/* Needs adjusting */
						tmp_start = range->start;
						flag = 0;

						while ((len_tmp = range->end - tmp_start) >= res->len) {
							if ((tmp_start % tmp_divide) == 0) {
								flag = 1;
								len_cur = len_tmp;
								start_cur = tmp_start;
								break;
							}
							tmp_start += tmp_divide - tmp_start % tmp_divide;
							if (tmp_start >= range->end)
								break;
						}
					}

					if (flag && len_cur == res->len) {
						res->start = start_cur;
						res->len += 1; /* To restore the balance */
						res->end = res->start + res->len - 1;
						return 0;
					}
				}
			}
			range = range->next;
		}		/* end of while */

		if ((!range) && (len_cur == 0)) {
			/* have gone through the list of devices and ranges and haven't found n.e.thing */
			err("no appropriate range.. bailing out...\n");
			return -EINVAL;
		} else if (len_cur) {
			res->start = start_cur;
			res->len += 1; /* To restore the balance */
			res->end = res->start + res->len - 1;
			return 0;
		}
	}

	if (!res_cur) {
		debug("prev->rangeno = %d, noranges = %d\n", res_prev->rangeno, noranges);
		if (res_prev->rangeno < noranges) {
			/* if there're more ranges out there to check */
			switch (res->type) {
				case IO:
					range = bus_cur->rangeIO;
					break;
				case MEM:
					range = bus_cur->rangeMem;
					break;
				case PFMEM:
					range = bus_cur->rangePFMem;
					break;
			}
			while (range) {
				len_tmp = range->end - range->start;

				if (len_tmp >= res->len) {
					if ((len_tmp < len_cur) || (len_cur == 0)) {
						if ((range->start % tmp_divide) == 0) {
							/* just perfect, starting address's divisible by length */
							flag = 1;
							len_cur = len_tmp;
							start_cur = range->start;
						} else {
							/* Needs adjusting */
							tmp_start = range->start;
							flag = 0;

							while ((len_tmp = range->end - tmp_start) >= res->len) {
								if ((tmp_start % tmp_divide) == 0) {
									flag = 1;
									len_cur = len_tmp;
									start_cur = tmp_start;
									break;
								}
								tmp_start += tmp_divide - tmp_start % tmp_divide;
								if (tmp_start >= range->end)
									break;
							}
						}

						if (flag && len_cur == res->len) {
							res->start = start_cur;
							res->len += 1; /* To restore the balance */
							res->end = res->start + res->len - 1;
							return 0;
						}
					}
				}
				range = range->next;
			}	/* end of while */

			if ((!range) && (len_cur == 0)) {
				/* have gone through the list of devices and ranges and haven't found n.e.thing */
				err("no appropriate range.. bailing out...\n");
				return -EINVAL;
			} else if (len_cur) {
				res->start = start_cur;
				res->len += 1; /* To restore the balance */
				res->end = res->start + res->len - 1;
				return 0;
			}
		} else {
			/* no more ranges to check on */
			if (len_cur) {
				res->start = start_cur;
				res->len += 1; /* To restore the balance */
				res->end = res->start + res->len - 1;
				return 0;
			} else {
				/* have gone through the list of devices and haven't found n.e.thing */
				err("no appropriate range.. bailing out...\n");
				return -EINVAL;
			}
		}
	}	/* end if (!res_cur) */
	return -EINVAL;
}