--- linux-2.6.9/drivers/atm/iphase.c.orig	2004-10-18 14:55:28.000000000 -0700
+++ linux-2.6.9/drivers/atm/iphase.c	2007-10-02 15:34:59.000000000 -0700
@@ -1306,8 +1306,7 @@
           {
              atomic_inc(&vcc->stats->rx_err);
              dev_kfree_skb_any(skb);
-             atm_return(vcc, atm_guess_pdu2truesize(len));
-             goto INCR_DLE;
+             atm_return(vcc, SKB_DATA_KMALLOC_BYTES(len));
            }
           // get real pkt length  pwang_test
           trailer = (struct cpcs_trailer*)((u_char *)skb->data +
@@ -1320,7 +1319,7 @@
              IF_ERR(printk("rx_dle_intr: Bad  AAL5 trailer %d (skb len %d)", 
                                                             length, skb->len);)
              dev_kfree_skb_any(skb);
-             atm_return(vcc, atm_guess_pdu2truesize(len));
+	     atm_return(vcc, SKB_DATA_KMALLOC_BYTES(len));
              goto INCR_DLE;
           }
           skb_trim(skb, length);
--- linux-2.6.9/include/linux/atmdev.h.orig	2007-10-02 15:17:29.000000000 -0700
+++ linux-2.6.9/include/linux/atmdev.h	2007-10-02 15:36:53.000000000 -0700
@@ -393,17 +393,6 @@
 void vcc_insert_socket(struct sock *sk);
 
 
-/*
- * This is approximately the algorithm used by alloc_skb.
- *
- */
-
-static inline int atm_guess_pdu2truesize(int size)
-{
-	return (SKB_DATA_ALIGN(size) + sizeof(struct skb_shared_info));
-}
-
-
 static inline void atm_force_charge(struct atm_vcc *vcc,int truesize)
 {
 	atomic_add(truesize, &vcc->sk->sk_rmem_alloc);
--- linux-2.6.9/include/linux/skbuff.h.orig	2007-10-02 15:17:27.000000000 -0700
+++ linux-2.6.9/include/linux/skbuff.h	2007-10-02 15:59:11.000000000 -0700
@@ -39,6 +39,10 @@
 
 #define SKB_DATA_ALIGN(X)	(((X) + (SMP_CACHE_BYTES - 1)) & \
 				 ~(SMP_CACHE_BYTES - 1))
+/* ip_append_data relies on this and SKB_MAX_ORDER matching so it masks
+    + * after adding in sizeof(skb_shared_info) */
+#define SKB_DATA_KMALLOC_BYTES(X) (SKB_DATA_ALIGN(X) + \
+               			sizeof(struct skb_shared_info))
 #define SKB_MAX_ORDER(X, ORDER)	(((PAGE_SIZE << (ORDER)) - (X) - \
 				  sizeof(struct skb_shared_info)) & \
 				  ~(SMP_CACHE_BYTES - 1))
--- linux-2.6.9/net/ipv4/ip_output.c.orig	2007-10-02 15:17:28.000000000 -0700
+++ linux-2.6.9/net/ipv4/ip_output.c	2007-10-02 15:48:32.000000000 -0700
@@ -825,32 +825,49 @@
 			datalen = length + fraggap;
 			if (datalen > mtu - fragheaderlen)
 				datalen = maxfraglen - fragheaderlen;
-			fraglen = datalen + fragheaderlen;
-
-			if ((flags & MSG_MORE) && 
-			    !(rt->u.dst.dev->features&NETIF_F_SG))
-				alloclen = mtu;
-			else
-				alloclen = datalen + fragheaderlen;
+			alloclen = fragheaderlen + hh_len + 15;
 
 			/* The last fragment gets additional space at tail.
 			 * Note, with MSG_MORE we overallocate on fragments,
 			 * because we have no idea what fragment will be
 			 * the last.
 			 */
-			if (datalen == length)
+			if (datalen == length + fraggap)
 				alloclen += rt->u.dst.trailer_len;
+                        if ((rt->u.dst.dev->features&NETIF_F_SG) &&
+                                (SKB_DATA_KMALLOC_BYTES(alloclen + datalen)
+                                                       > PAGE_SIZE))
+                        {
+                                /*
+                                * datalen is shrinking so there won't be a
+                                * trailer, but alloclen already accounted for
+                                * it.  use that space for payload but only if 
+                                * it wasn't the trailer_len itself that pushed
+                                * the alloc beyond a single page.
+                                */
+                                if (datalen - SKB_MAX_ORDER(alloclen, 0) >
+                                        rt->u.dst.trailer_len)
+
+                                       alloclen -= rt->u.dst.trailer_len;
+
+                                       datalen = SKB_MAX_ORDER(alloclen, 0);
+                       }
+
+                       fraglen = datalen + fragheaderlen;
+
+                       if ((flags & MSG_MORE) &&
+                           !(rt->u.dst.dev->features&NETIF_F_SG))
+                               alloclen += mtu - fragheaderlen;
+                       else
+                               alloclen += datalen;
 
 			if (transhdrlen) {
-				skb = sock_alloc_send_skb(sk, 
-						alloclen + hh_len + 15,
+				skb = sock_alloc_send_skb(sk, alloclen,
 						(flags & MSG_DONTWAIT), &err);
 			} else {
 				skb = NULL;
-				if (atomic_read(&sk->sk_wmem_alloc) <=
-				    2 * sk->sk_sndbuf)
-					skb = sock_wmalloc(sk, 
-							   alloclen + hh_len + 15, 1,
+				if (atomic_read(&sk->sk_wmem_alloc) <= 2 * sk->sk_sndbuf)
+					skb = sock_wmalloc(sk, alloclen, 1,
 							   sk->sk_allocation);
 				if (unlikely(skb == NULL))
 					err = -ENOBUFS;
--- linux-2.6.9/net/core/skbuff.c.orig	2007-10-02 15:17:27.000000000 -0700
+++ linux-2.6.9/net/core/skbuff.c	2007-10-02 16:09:14.000000000 -0700
@@ -140,6 +140,7 @@
 	/* Get the DATA. Size must match skb_add_mtu(). */
 	size = SKB_DATA_ALIGN(size);
 	data = kmalloc(size + sizeof(struct skb_shared_info), gfp_mask);
+
 	if (!data)
 		goto nodata;
 
--- linux-2.6.9/net/atm/atm_misc.c.orig	2007-10-02 15:52:01.000000000 -0700
+++ linux-2.6.9/net/atm/atm_misc.c	2007-10-02 15:53:01.000000000 -0700
@@ -27,7 +27,7 @@
 struct sk_buff *atm_alloc_charge(struct atm_vcc *vcc,int pdu_size,
     int gfp_flags)
 {
-	int guess = atm_guess_pdu2truesize(pdu_size);
+        int guess = SKB_DATA_KMALLOC_BYTES(pdu_size);
 
 	atm_force_charge(vcc,guess);
 	if (atomic_read(&vcc->sk->sk_rmem_alloc) <= vcc->sk->sk_rcvbuf) {
