]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - drivers/s390/cio/itcw.c
Merge tag 'v2.6.38' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6
[mv-sheeva.git] / drivers / s390 / cio / itcw.c
index a0ae29564774c7267654138e7a32beab87201f44..358ee16d10a2e97375df34fd84fde7b404adcc78 100644 (file)
@@ -93,6 +93,7 @@ EXPORT_SYMBOL(itcw_get_tcw);
 size_t itcw_calc_size(int intrg, int max_tidaws, int intrg_max_tidaws)
 {
        size_t len;
+       int cross_count;
 
        /* Main data. */
        len = sizeof(struct itcw);
@@ -105,12 +106,27 @@ size_t itcw_calc_size(int intrg, int max_tidaws, int intrg_max_tidaws)
                       /* TSB */ sizeof(struct tsb) +
                       /* TIDAL */ intrg_max_tidaws * sizeof(struct tidaw);
        }
+
        /* Maximum required alignment padding. */
        len += /* Initial TCW */ 63 + /* Interrogate TCCB */ 7;
-       /* Maximum padding for structures that may not cross 4k boundary. */
-       if ((max_tidaws > 0) || (intrg_max_tidaws > 0))
-               len += max(max_tidaws, intrg_max_tidaws) *
-                      sizeof(struct tidaw) - 1;
+
+       /* TIDAW lists may not cross a 4k boundary. To cross a
+        * boundary we need to add a TTIC TIDAW. We need to reserve
+        * one additional TIDAW for a TTIC that we may need to add due
+        * to the placement of the data chunk in memory, and a further
+        * TIDAW for each page boundary that the TIDAW list may cross
+        * due to it's own size.
+        */
+       if (max_tidaws) {
+               cross_count = 1 + ((max_tidaws * sizeof(struct tidaw) - 1)
+                                  >> PAGE_SHIFT);
+               len += cross_count * sizeof(struct tidaw);
+       }
+       if (intrg_max_tidaws) {
+               cross_count = 1 + ((intrg_max_tidaws * sizeof(struct tidaw) - 1)
+                                  >> PAGE_SHIFT);
+               len += cross_count * sizeof(struct tidaw);
+       }
        return len;
 }
 EXPORT_SYMBOL(itcw_calc_size);
@@ -165,6 +181,7 @@ struct itcw *itcw_init(void *buffer, size_t size, int op, int intrg,
        void *chunk;
        addr_t start;
        addr_t end;
+       int cross_count;
 
        /* Check for 2G limit. */
        start = (addr_t) buffer;
@@ -177,8 +194,17 @@ struct itcw *itcw_init(void *buffer, size_t size, int op, int intrg,
        if (IS_ERR(chunk))
                return chunk;
        itcw = chunk;
-       itcw->max_tidaws = max_tidaws;
-       itcw->intrg_max_tidaws = intrg_max_tidaws;
+       /* allow for TTIC tidaws that may be needed to cross a page boundary */
+       cross_count = 0;
+       if (max_tidaws)
+               cross_count = 1 + ((max_tidaws * sizeof(struct tidaw) - 1)
+                                  >> PAGE_SHIFT);
+       itcw->max_tidaws = max_tidaws + cross_count;
+       cross_count = 0;
+       if (intrg_max_tidaws)
+               cross_count = 1 + ((intrg_max_tidaws * sizeof(struct tidaw) - 1)
+                                  >> PAGE_SHIFT);
+       itcw->intrg_max_tidaws = intrg_max_tidaws + cross_count;
        /* Main TCW. */
        chunk = fit_chunk(&start, end, sizeof(struct tcw), 64, 0);
        if (IS_ERR(chunk))
@@ -198,7 +224,7 @@ struct itcw *itcw_init(void *buffer, size_t size, int op, int intrg,
        /* Data TIDAL. */
        if (max_tidaws > 0) {
                chunk = fit_chunk(&start, end, sizeof(struct tidaw) *
-                                 max_tidaws, 16, 1);
+                                 itcw->max_tidaws, 16, 0);
                if (IS_ERR(chunk))
                        return chunk;
                tcw_set_data(itcw->tcw, chunk, 1);
@@ -206,7 +232,7 @@ struct itcw *itcw_init(void *buffer, size_t size, int op, int intrg,
        /* Interrogate data TIDAL. */
        if (intrg && (intrg_max_tidaws > 0)) {
                chunk = fit_chunk(&start, end, sizeof(struct tidaw) *
-                                 intrg_max_tidaws, 16, 1);
+                                 itcw->intrg_max_tidaws, 16, 0);
                if (IS_ERR(chunk))
                        return chunk;
                tcw_set_data(itcw->intrg_tcw, chunk, 1);
@@ -283,13 +309,29 @@ EXPORT_SYMBOL(itcw_add_dcw);
  * the new tidaw on success or -%ENOSPC if the new tidaw would exceed the
  * available space.
  *
- * Note: the tidaw-list is assumed to be contiguous with no ttics. The
- * last-tidaw flag for the last tidaw in the list will be set by itcw_finalize.
+ * Note: TTIC tidaws are automatically added when needed, so explicitly calling
+ * this interface with the TTIC flag is not supported. The last-tidaw flag
+ * for the last tidaw in the list will be set by itcw_finalize.
  */
 struct tidaw *itcw_add_tidaw(struct itcw *itcw, u8 flags, void *addr, u32 count)
 {
+       struct tidaw *following;
+
        if (itcw->num_tidaws >= itcw->max_tidaws)
                return ERR_PTR(-ENOSPC);
+       /*
+        * Is the tidaw, which follows the one we are about to fill, on the next
+        * page? Then we have to insert a TTIC tidaw first, that points to the
+        * tidaw on the new page.
+        */
+       following = ((struct tidaw *) tcw_get_data(itcw->tcw))
+               + itcw->num_tidaws + 1;
+       if (itcw->num_tidaws && !((unsigned long) following & ~PAGE_MASK)) {
+               tcw_add_tidaw(itcw->tcw, itcw->num_tidaws++,
+                             TIDAW_FLAGS_TTIC, following, 0);
+               if (itcw->num_tidaws >= itcw->max_tidaws)
+                       return ERR_PTR(-ENOSPC);
+       }
        return tcw_add_tidaw(itcw->tcw, itcw->num_tidaws++, flags, addr, count);
 }
 EXPORT_SYMBOL(itcw_add_tidaw);