3495 lines
103 KiB
Diff
3495 lines
103 KiB
Diff
diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c
|
|
index d91fb8c..aa24f2f 100644
|
|
--- a/drivers/gpu/drm/drm_crtc.c
|
|
+++ b/drivers/gpu/drm/drm_crtc.c
|
|
@@ -33,6 +33,7 @@
|
|
#include "drm.h"
|
|
#include "drmP.h"
|
|
#include "drm_crtc.h"
|
|
+#include "drm_edid.h"
|
|
|
|
struct drm_prop_enum_list {
|
|
int type;
|
|
@@ -2349,7 +2350,7 @@ int drm_mode_connector_update_edid_property(struct drm_connector *connector,
|
|
struct edid *edid)
|
|
{
|
|
struct drm_device *dev = connector->dev;
|
|
- int ret = 0;
|
|
+ int ret = 0, size;
|
|
|
|
if (connector->edid_blob_ptr)
|
|
drm_property_destroy_blob(dev, connector->edid_blob_ptr);
|
|
@@ -2361,7 +2362,9 @@ int drm_mode_connector_update_edid_property(struct drm_connector *connector,
|
|
return ret;
|
|
}
|
|
|
|
- connector->edid_blob_ptr = drm_property_create_blob(connector->dev, 128, edid);
|
|
+ size = EDID_LENGTH * (1 + edid->extensions);
|
|
+ connector->edid_blob_ptr = drm_property_create_blob(connector->dev,
|
|
+ size, edid);
|
|
|
|
ret = drm_connector_property_set_value(connector,
|
|
dev->mode_config.edid_property,
|
|
diff --git a/drivers/gpu/drm/drm_crtc_helper.c b/drivers/gpu/drm/drm_crtc_helper.c
|
|
index 7d0f00a..51103aa 100644
|
|
--- a/drivers/gpu/drm/drm_crtc_helper.c
|
|
+++ b/drivers/gpu/drm/drm_crtc_helper.c
|
|
@@ -104,6 +104,7 @@ int drm_helper_probe_single_connector_modes(struct drm_connector *connector,
|
|
if (connector->status == connector_status_disconnected) {
|
|
DRM_DEBUG_KMS("%s is disconnected\n",
|
|
drm_get_connector_name(connector));
|
|
+ drm_mode_connector_update_edid_property(connector, NULL);
|
|
goto prune;
|
|
}
|
|
|
|
@@ -836,11 +837,7 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set)
|
|
mode_changed = true;
|
|
} else if (set->fb == NULL) {
|
|
mode_changed = true;
|
|
- } else if ((set->fb->bits_per_pixel !=
|
|
- set->crtc->fb->bits_per_pixel) ||
|
|
- set->fb->depth != set->crtc->fb->depth)
|
|
- fb_changed = true;
|
|
- else
|
|
+ } else
|
|
fb_changed = true;
|
|
}
|
|
|
|
diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c
|
|
index 766c468..f3c58e2 100644
|
|
--- a/drivers/gpu/drm/drm_drv.c
|
|
+++ b/drivers/gpu/drm/drm_drv.c
|
|
@@ -125,28 +125,28 @@ static struct drm_ioctl_desc drm_ioctls[] = {
|
|
|
|
DRM_IOCTL_DEF(DRM_IOCTL_UPDATE_DRAW, drm_update_drawable_info, DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
|
|
|
|
- DRM_IOCTL_DEF(DRM_IOCTL_GEM_CLOSE, drm_gem_close_ioctl, 0),
|
|
- DRM_IOCTL_DEF(DRM_IOCTL_GEM_FLINK, drm_gem_flink_ioctl, DRM_AUTH),
|
|
- DRM_IOCTL_DEF(DRM_IOCTL_GEM_OPEN, drm_gem_open_ioctl, DRM_AUTH),
|
|
-
|
|
- DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETRESOURCES, drm_mode_getresources, DRM_MASTER|DRM_CONTROL_ALLOW),
|
|
- DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETCRTC, drm_mode_getcrtc, DRM_MASTER|DRM_CONTROL_ALLOW),
|
|
- DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETCRTC, drm_mode_setcrtc, DRM_MASTER|DRM_CONTROL_ALLOW),
|
|
- DRM_IOCTL_DEF(DRM_IOCTL_MODE_CURSOR, drm_mode_cursor_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW),
|
|
- DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETGAMMA, drm_mode_gamma_get_ioctl, DRM_MASTER),
|
|
- DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETGAMMA, drm_mode_gamma_set_ioctl, DRM_MASTER),
|
|
- DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETENCODER, drm_mode_getencoder, DRM_MASTER|DRM_CONTROL_ALLOW),
|
|
- DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETCONNECTOR, drm_mode_getconnector, DRM_MASTER|DRM_CONTROL_ALLOW),
|
|
- DRM_IOCTL_DEF(DRM_IOCTL_MODE_ATTACHMODE, drm_mode_attachmode_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW),
|
|
- DRM_IOCTL_DEF(DRM_IOCTL_MODE_DETACHMODE, drm_mode_detachmode_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW),
|
|
- DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPROPERTY, drm_mode_getproperty_ioctl, DRM_MASTER | DRM_CONTROL_ALLOW),
|
|
- DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETPROPERTY, drm_mode_connector_property_set_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW),
|
|
- DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPROPBLOB, drm_mode_getblob_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW),
|
|
- DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETFB, drm_mode_getfb, DRM_MASTER|DRM_CONTROL_ALLOW),
|
|
- DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb, DRM_MASTER|DRM_CONTROL_ALLOW),
|
|
- DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb, DRM_MASTER|DRM_CONTROL_ALLOW),
|
|
- DRM_IOCTL_DEF(DRM_IOCTL_MODE_PAGE_FLIP, drm_mode_page_flip_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW),
|
|
- DRM_IOCTL_DEF(DRM_IOCTL_MODE_DIRTYFB, drm_mode_dirtyfb_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW)
|
|
+ DRM_IOCTL_DEF(DRM_IOCTL_GEM_CLOSE, drm_gem_close_ioctl, DRM_UNLOCKED),
|
|
+ DRM_IOCTL_DEF(DRM_IOCTL_GEM_FLINK, drm_gem_flink_ioctl, DRM_AUTH|DRM_UNLOCKED),
|
|
+ DRM_IOCTL_DEF(DRM_IOCTL_GEM_OPEN, drm_gem_open_ioctl, DRM_AUTH|DRM_UNLOCKED),
|
|
+
|
|
+ DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETRESOURCES, drm_mode_getresources, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
|
|
+ DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETCRTC, drm_mode_getcrtc, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
|
|
+ DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETCRTC, drm_mode_setcrtc, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
|
|
+ DRM_IOCTL_DEF(DRM_IOCTL_MODE_CURSOR, drm_mode_cursor_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
|
|
+ DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETGAMMA, drm_mode_gamma_get_ioctl, DRM_MASTER|DRM_UNLOCKED),
|
|
+ DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETGAMMA, drm_mode_gamma_set_ioctl, DRM_MASTER|DRM_UNLOCKED),
|
|
+ DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETENCODER, drm_mode_getencoder, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
|
|
+ DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETCONNECTOR, drm_mode_getconnector, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
|
|
+ DRM_IOCTL_DEF(DRM_IOCTL_MODE_ATTACHMODE, drm_mode_attachmode_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
|
|
+ DRM_IOCTL_DEF(DRM_IOCTL_MODE_DETACHMODE, drm_mode_detachmode_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
|
|
+ DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPROPERTY, drm_mode_getproperty_ioctl, DRM_MASTER | DRM_CONTROL_ALLOW|DRM_UNLOCKED),
|
|
+ DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETPROPERTY, drm_mode_connector_property_set_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
|
|
+ DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPROPBLOB, drm_mode_getblob_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
|
|
+ DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETFB, drm_mode_getfb, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
|
|
+ DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
|
|
+ DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
|
|
+ DRM_IOCTL_DEF(DRM_IOCTL_MODE_PAGE_FLIP, drm_mode_page_flip_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
|
|
+ DRM_IOCTL_DEF(DRM_IOCTL_MODE_DIRTYFB, drm_mode_dirtyfb_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED)
|
|
};
|
|
|
|
#define DRM_CORE_IOCTL_COUNT ARRAY_SIZE( drm_ioctls )
|
|
diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c
|
|
index ab6c973..4ea2721 100644
|
|
--- a/drivers/gpu/drm/drm_edid.c
|
|
+++ b/drivers/gpu/drm/drm_edid.c
|
|
@@ -2,6 +2,7 @@
|
|
* Copyright (c) 2006 Luc Verhaegen (quirks list)
|
|
* Copyright (c) 2007-2008 Intel Corporation
|
|
* Jesse Barnes <jesse.barnes@intel.com>
|
|
+ * Copyright 2010 Red Hat, Inc.
|
|
*
|
|
* DDC probing routines (drm_ddc_read & drm_do_probe_ddc_edid) originally from
|
|
* FB layer.
|
|
@@ -32,10 +33,9 @@
|
|
#include "drmP.h"
|
|
#include "drm_edid.h"
|
|
|
|
-/*
|
|
- * TODO:
|
|
- * - support EDID 1.4 (incl. CE blocks)
|
|
- */
|
|
+#define EDID_EST_TIMINGS 16
|
|
+#define EDID_STD_TIMINGS 8
|
|
+#define EDID_DETAILED_TIMINGS 4
|
|
|
|
/*
|
|
* EDID blocks out in the wild have a variety of bugs, try to collect
|
|
@@ -65,7 +65,8 @@
|
|
|
|
#define LEVEL_DMT 0
|
|
#define LEVEL_GTF 1
|
|
-#define LEVEL_CVT 2
|
|
+#define LEVEL_GTF2 2
|
|
+#define LEVEL_CVT 3
|
|
|
|
static struct edid_quirk {
|
|
char *vendor;
|
|
@@ -85,6 +86,8 @@ static struct edid_quirk {
|
|
|
|
/* Envision Peripherals, Inc. EN-7100e */
|
|
{ "EPI", 59264, EDID_QUIRK_135_CLOCK_TOO_HIGH },
|
|
+ /* Envision EN2028 */
|
|
+ { "EPI", 8232, EDID_QUIRK_PREFER_LARGE_60 },
|
|
|
|
/* Funai Electronics PM36B */
|
|
{ "FCM", 13600, EDID_QUIRK_PREFER_LARGE_75 |
|
|
@@ -107,36 +110,38 @@ static struct edid_quirk {
|
|
{ "SAM", 638, EDID_QUIRK_PREFER_LARGE_60 },
|
|
};
|
|
|
|
+/*** DDC fetch and block validation ***/
|
|
|
|
-/* Valid EDID header has these bytes */
|
|
static const u8 edid_header[] = {
|
|
0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00
|
|
};
|
|
|
|
-/**
|
|
- * edid_is_valid - sanity check EDID data
|
|
- * @edid: EDID data
|
|
- *
|
|
- * Sanity check the EDID block by looking at the header, the version number
|
|
- * and the checksum. Return 0 if the EDID doesn't check out, or 1 if it's
|
|
- * valid.
|
|
+/*
|
|
+ * Sanity check the EDID block (base or extension). Return 0 if the block
|
|
+ * doesn't check out, or 1 if it's valid.
|
|
*/
|
|
-static bool edid_is_valid(struct edid *edid)
|
|
+static bool
|
|
+drm_edid_block_valid(u8 *raw_edid)
|
|
{
|
|
- int i, score = 0;
|
|
+ int i;
|
|
u8 csum = 0;
|
|
- u8 *raw_edid = (u8 *)edid;
|
|
+ struct edid *edid = (struct edid *)raw_edid;
|
|
|
|
- for (i = 0; i < sizeof(edid_header); i++)
|
|
- if (raw_edid[i] == edid_header[i])
|
|
- score++;
|
|
+ if (raw_edid[0] == 0x00) {
|
|
+ int score = 0;
|
|
|
|
- if (score == 8) ;
|
|
- else if (score >= 6) {
|
|
- DRM_DEBUG("Fixing EDID header, your hardware may be failing\n");
|
|
- memcpy(raw_edid, edid_header, sizeof(edid_header));
|
|
- } else
|
|
- goto bad;
|
|
+ for (i = 0; i < sizeof(edid_header); i++)
|
|
+ if (raw_edid[i] == edid_header[i])
|
|
+ score++;
|
|
+
|
|
+ if (score == 8) ;
|
|
+ else if (score >= 6) {
|
|
+ DRM_DEBUG("Fixing EDID header, your hardware may be failing\n");
|
|
+ memcpy(raw_edid, edid_header, sizeof(edid_header));
|
|
+ } else {
|
|
+ goto bad;
|
|
+ }
|
|
+ }
|
|
|
|
for (i = 0; i < EDID_LENGTH; i++)
|
|
csum += raw_edid[i];
|
|
@@ -145,13 +150,21 @@ static bool edid_is_valid(struct edid *edid)
|
|
goto bad;
|
|
}
|
|
|
|
- if (edid->version != 1) {
|
|
- DRM_ERROR("EDID has major version %d, instead of 1\n", edid->version);
|
|
- goto bad;
|
|
- }
|
|
+ /* per-block-type checks */
|
|
+ switch (raw_edid[0]) {
|
|
+ case 0: /* base */
|
|
+ if (edid->version != 1) {
|
|
+ DRM_ERROR("EDID has major version %d, instead of 1\n", edid->version);
|
|
+ goto bad;
|
|
+ }
|
|
|
|
- if (edid->revision > 4)
|
|
- DRM_DEBUG("EDID minor > 4, assuming backward compatibility\n");
|
|
+ if (edid->revision > 4)
|
|
+ DRM_DEBUG("EDID minor > 4, assuming backward compatibility\n");
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
|
|
return 1;
|
|
|
|
@@ -165,6 +178,157 @@ bad:
|
|
}
|
|
|
|
/**
|
|
+ * drm_edid_is_valid - sanity check EDID data
|
|
+ * @edid: EDID data
|
|
+ *
|
|
+ * Sanity-check an entire EDID record (including extensions)
|
|
+ */
|
|
+bool drm_edid_is_valid(struct edid *edid)
|
|
+{
|
|
+ int i;
|
|
+ u8 *raw = (u8 *)edid;
|
|
+
|
|
+ if (!edid)
|
|
+ return false;
|
|
+
|
|
+ for (i = 0; i <= edid->extensions; i++)
|
|
+ if (!drm_edid_block_valid(raw + i * EDID_LENGTH))
|
|
+ return false;
|
|
+
|
|
+ return true;
|
|
+}
|
|
+EXPORT_SYMBOL(drm_edid_is_valid);
|
|
+
|
|
+#define DDC_ADDR 0x50
|
|
+#define DDC_SEGMENT_ADDR 0x30
|
|
+/**
|
|
+ * Get EDID information via I2C.
|
|
+ *
|
|
+ * \param adapter : i2c device adaptor
|
|
+ * \param buf : EDID data buffer to be filled
|
|
+ * \param len : EDID data buffer length
|
|
+ * \return 0 on success or -1 on failure.
|
|
+ *
|
|
+ * Try to fetch EDID information by calling i2c driver function.
|
|
+ */
|
|
+static int
|
|
+drm_do_probe_ddc_edid(struct i2c_adapter *adapter, unsigned char *buf,
|
|
+ int block, int len)
|
|
+{
|
|
+ unsigned char start = block * EDID_LENGTH;
|
|
+ struct i2c_msg msgs[] = {
|
|
+ {
|
|
+ .addr = DDC_ADDR,
|
|
+ .flags = 0,
|
|
+ .len = 1,
|
|
+ .buf = &start,
|
|
+ }, {
|
|
+ .addr = DDC_ADDR,
|
|
+ .flags = I2C_M_RD,
|
|
+ .len = len,
|
|
+ .buf = buf + start,
|
|
+ }
|
|
+ };
|
|
+
|
|
+ if (i2c_transfer(adapter, msgs, 2) == 2)
|
|
+ return 0;
|
|
+
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+static u8 *
|
|
+drm_do_get_edid(struct drm_connector *connector, struct i2c_adapter *adapter)
|
|
+{
|
|
+ int i, j = 0;
|
|
+ u8 *block, *new;
|
|
+
|
|
+ if ((block = kmalloc(EDID_LENGTH, GFP_KERNEL)) == NULL)
|
|
+ return NULL;
|
|
+
|
|
+ /* base block fetch */
|
|
+ for (i = 0; i < 4; i++) {
|
|
+ if (drm_do_probe_ddc_edid(adapter, block, 0, EDID_LENGTH))
|
|
+ goto out;
|
|
+ if (drm_edid_block_valid(block))
|
|
+ break;
|
|
+ }
|
|
+ if (i == 4)
|
|
+ goto carp;
|
|
+
|
|
+ /* if there's no extensions, we're done */
|
|
+ if (block[0x7e] == 0)
|
|
+ return block;
|
|
+
|
|
+ new = krealloc(block, (block[0x7e] + 1) * EDID_LENGTH, GFP_KERNEL);
|
|
+ if (!new)
|
|
+ goto out;
|
|
+ block = new;
|
|
+
|
|
+ for (j = 1; j <= block[0x7e]; j++) {
|
|
+ for (i = 0; i < 4; i++) {
|
|
+ if (drm_do_probe_ddc_edid(adapter, block, j,
|
|
+ EDID_LENGTH))
|
|
+ goto out;
|
|
+ if (drm_edid_block_valid(block + j * EDID_LENGTH))
|
|
+ break;
|
|
+ }
|
|
+ if (i == 4)
|
|
+ goto carp;
|
|
+ }
|
|
+
|
|
+ return block;
|
|
+
|
|
+carp:
|
|
+ dev_warn(&connector->dev->pdev->dev, "%s: EDID block %d invalid.\n",
|
|
+ drm_get_connector_name(connector), j);
|
|
+
|
|
+out:
|
|
+ kfree(block);
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Probe DDC presence.
|
|
+ *
|
|
+ * \param adapter : i2c device adaptor
|
|
+ * \return 1 on success
|
|
+ */
|
|
+static bool
|
|
+drm_probe_ddc(struct i2c_adapter *adapter)
|
|
+{
|
|
+ unsigned char out;
|
|
+
|
|
+ return (drm_do_probe_ddc_edid(adapter, &out, 0, 1) == 0);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * drm_get_edid - get EDID data, if available
|
|
+ * @connector: connector we're probing
|
|
+ * @adapter: i2c adapter to use for DDC
|
|
+ *
|
|
+ * Poke the given i2c channel to grab EDID data if possible. If found,
|
|
+ * attach it to the connector.
|
|
+ *
|
|
+ * Return edid data or NULL if we couldn't find any.
|
|
+ */
|
|
+struct edid *drm_get_edid(struct drm_connector *connector,
|
|
+ struct i2c_adapter *adapter)
|
|
+{
|
|
+ struct edid *edid = NULL;
|
|
+
|
|
+ if (drm_probe_ddc(adapter))
|
|
+ edid = (struct edid *)drm_do_get_edid(connector, adapter);
|
|
+
|
|
+ connector->display_info.raw_edid = (char *)edid;
|
|
+
|
|
+ return edid;
|
|
+
|
|
+}
|
|
+EXPORT_SYMBOL(drm_get_edid);
|
|
+
|
|
+/*** EDID parsing ***/
|
|
+
|
|
+/**
|
|
* edid_vendor - match a string against EDID's obfuscated vendor field
|
|
* @edid: EDID to match
|
|
* @vendor: vendor string
|
|
@@ -514,6 +678,110 @@ static struct drm_display_mode *drm_find_dmt(struct drm_device *dev,
|
|
return mode;
|
|
}
|
|
|
|
+typedef void detailed_cb(struct detailed_timing *timing, void *closure);
|
|
+
|
|
+static void
|
|
+drm_for_each_detailed_block(u8 *raw_edid, detailed_cb *cb, void *closure)
|
|
+{
|
|
+ int i;
|
|
+ struct edid *edid = (struct edid *)raw_edid;
|
|
+
|
|
+ if (edid == NULL)
|
|
+ return;
|
|
+
|
|
+ for (i = 0; i < EDID_DETAILED_TIMINGS; i++)
|
|
+ cb(&(edid->detailed_timings[i]), closure);
|
|
+
|
|
+ /* XXX extension block walk */
|
|
+}
|
|
+
|
|
+static void
|
|
+is_rb(struct detailed_timing *t, void *data)
|
|
+{
|
|
+ u8 *r = (u8 *)t;
|
|
+ if (r[3] == EDID_DETAIL_MONITOR_RANGE)
|
|
+ if (r[15] & 0x10)
|
|
+ *(bool *)data = true;
|
|
+}
|
|
+
|
|
+/* EDID 1.4 defines this explicitly. For EDID 1.3, we guess, badly. */
|
|
+static bool
|
|
+drm_monitor_supports_rb(struct edid *edid)
|
|
+{
|
|
+ if (edid->revision >= 4) {
|
|
+ bool ret;
|
|
+ drm_for_each_detailed_block((u8 *)edid, is_rb, &ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return ((edid->input & DRM_EDID_INPUT_DIGITAL) != 0);
|
|
+}
|
|
+
|
|
+static void
|
|
+find_gtf2(struct detailed_timing *t, void *data)
|
|
+{
|
|
+ u8 *r = (u8 *)t;
|
|
+ if (r[3] == EDID_DETAIL_MONITOR_RANGE && r[10] == 0x02)
|
|
+ *(u8 **)data = r;
|
|
+}
|
|
+
|
|
+/* Secondary GTF curve kicks in above some break frequency */
|
|
+static int
|
|
+drm_gtf2_hbreak(struct edid *edid)
|
|
+{
|
|
+ u8 *r = NULL;
|
|
+ drm_for_each_detailed_block((u8 *)edid, find_gtf2, &r);
|
|
+ return r ? (r[12] * 2) : 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+drm_gtf2_2c(struct edid *edid)
|
|
+{
|
|
+ u8 *r = NULL;
|
|
+ drm_for_each_detailed_block((u8 *)edid, find_gtf2, &r);
|
|
+ return r ? r[13] : 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+drm_gtf2_m(struct edid *edid)
|
|
+{
|
|
+ u8 *r = NULL;
|
|
+ drm_for_each_detailed_block((u8 *)edid, find_gtf2, &r);
|
|
+ return r ? (r[15] << 8) + r[14] : 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+drm_gtf2_k(struct edid *edid)
|
|
+{
|
|
+ u8 *r = NULL;
|
|
+ drm_for_each_detailed_block((u8 *)edid, find_gtf2, &r);
|
|
+ return r ? r[16] : 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+drm_gtf2_2j(struct edid *edid)
|
|
+{
|
|
+ u8 *r = NULL;
|
|
+ drm_for_each_detailed_block((u8 *)edid, find_gtf2, &r);
|
|
+ return r ? r[17] : 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * standard_timing_level - get std. timing level(CVT/GTF/DMT)
|
|
+ * @edid: EDID block to scan
|
|
+ */
|
|
+static int standard_timing_level(struct edid *edid)
|
|
+{
|
|
+ if (edid->revision >= 2) {
|
|
+ if (edid->revision >= 4 && (edid->features & DRM_EDID_FEATURE_DEFAULT_GTF))
|
|
+ return LEVEL_CVT;
|
|
+ if (drm_gtf2_hbreak(edid))
|
|
+ return LEVEL_GTF2;
|
|
+ return LEVEL_GTF;
|
|
+ }
|
|
+ return LEVEL_DMT;
|
|
+}
|
|
+
|
|
/*
|
|
* 0 is reserved. The spec says 0x01 fill for unused timings. Some old
|
|
* monitors fill with ascii space (0x20) instead.
|
|
@@ -533,22 +801,20 @@ bad_std_timing(u8 a, u8 b)
|
|
*
|
|
* Take the standard timing params (in this case width, aspect, and refresh)
|
|
* and convert them into a real mode using CVT/GTF/DMT.
|
|
- *
|
|
- * Punts for now, but should eventually use the FB layer's CVT based mode
|
|
- * generation code.
|
|
*/
|
|
-struct drm_display_mode *drm_mode_std(struct drm_device *dev,
|
|
- struct std_timing *t,
|
|
- int revision,
|
|
- int timing_level)
|
|
+static struct drm_display_mode *
|
|
+drm_mode_std(struct drm_connector *connector, struct edid *edid,
|
|
+ struct std_timing *t, int revision)
|
|
{
|
|
- struct drm_display_mode *mode;
|
|
+ struct drm_device *dev = connector->dev;
|
|
+ struct drm_display_mode *m, *mode = NULL;
|
|
int hsize, vsize;
|
|
int vrefresh_rate;
|
|
unsigned aspect_ratio = (t->vfreq_aspect & EDID_TIMING_ASPECT_MASK)
|
|
>> EDID_TIMING_ASPECT_SHIFT;
|
|
unsigned vfreq = (t->vfreq_aspect & EDID_TIMING_VFREQ_MASK)
|
|
>> EDID_TIMING_VFREQ_SHIFT;
|
|
+ int timing_level = standard_timing_level(edid);
|
|
|
|
if (bad_std_timing(t->hsize, t->vfreq_aspect))
|
|
return NULL;
|
|
@@ -569,16 +835,36 @@ struct drm_display_mode *drm_mode_std(struct drm_device *dev,
|
|
vsize = (hsize * 4) / 5;
|
|
else
|
|
vsize = (hsize * 9) / 16;
|
|
- /* HDTV hack */
|
|
- if (hsize == 1360 && vsize == 765 && vrefresh_rate == 60) {
|
|
- mode = drm_cvt_mode(dev, hsize, vsize, vrefresh_rate, 0, 0,
|
|
+
|
|
+ /* HDTV hack, part 1 */
|
|
+ if (vrefresh_rate == 60 &&
|
|
+ ((hsize == 1360 && vsize == 765) ||
|
|
+ (hsize == 1368 && vsize == 769))) {
|
|
+ hsize = 1366;
|
|
+ vsize = 768;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * If this connector already has a mode for this size and refresh
|
|
+ * rate (because it came from detailed or CVT info), use that
|
|
+ * instead. This way we don't have to guess at interlace or
|
|
+ * reduced blanking.
|
|
+ */
|
|
+ list_for_each_entry(m, &connector->probed_modes, head)
|
|
+ if (m->hdisplay == hsize && m->vdisplay == vsize &&
|
|
+ drm_mode_vrefresh(m) == vrefresh_rate)
|
|
+ return NULL;
|
|
+
|
|
+ /* HDTV hack, part 2 */
|
|
+ if (hsize == 1366 && vsize == 768 && vrefresh_rate == 60) {
|
|
+ mode = drm_cvt_mode(dev, 1366, 768, vrefresh_rate, 0, 0,
|
|
false);
|
|
mode->hdisplay = 1366;
|
|
mode->vsync_start = mode->vsync_start - 1;
|
|
mode->vsync_end = mode->vsync_end - 1;
|
|
return mode;
|
|
}
|
|
- mode = NULL;
|
|
+
|
|
/* check whether it can be found in default mode table */
|
|
mode = drm_find_dmt(dev, hsize, vsize, vrefresh_rate);
|
|
if (mode)
|
|
@@ -590,6 +876,23 @@ struct drm_display_mode *drm_mode_std(struct drm_device *dev,
|
|
case LEVEL_GTF:
|
|
mode = drm_gtf_mode(dev, hsize, vsize, vrefresh_rate, 0, 0);
|
|
break;
|
|
+ case LEVEL_GTF2:
|
|
+ /*
|
|
+ * This is potentially wrong if there's ever a monitor with
|
|
+ * more than one ranges section, each claiming a different
|
|
+ * secondary GTF curve. Please don't do that.
|
|
+ */
|
|
+ mode = drm_gtf_mode(dev, hsize, vsize, vrefresh_rate, 0, 0);
|
|
+ if (drm_mode_hsync(mode) > drm_gtf2_hbreak(edid)) {
|
|
+ kfree(mode);
|
|
+ mode = drm_gtf_mode_complex(dev, hsize, vsize,
|
|
+ vrefresh_rate, 0, 0,
|
|
+ drm_gtf2_m(edid),
|
|
+ drm_gtf2_2c(edid),
|
|
+ drm_gtf2_k(edid),
|
|
+ drm_gtf2_2j(edid));
|
|
+ }
|
|
+ break;
|
|
case LEVEL_CVT:
|
|
mode = drm_cvt_mode(dev, hsize, vsize, vrefresh_rate, 0, 0,
|
|
false);
|
|
@@ -707,25 +1010,16 @@ static struct drm_display_mode *drm_mode_detailed(struct drm_device *dev,
|
|
mode->vsync_end = mode->vsync_start + vsync_pulse_width;
|
|
mode->vtotal = mode->vdisplay + vblank;
|
|
|
|
- /* perform the basic check for the detailed timing */
|
|
- if (mode->hsync_end > mode->htotal ||
|
|
- mode->vsync_end > mode->vtotal) {
|
|
- drm_mode_destroy(dev, mode);
|
|
- DRM_DEBUG_KMS("Incorrect detailed timing. "
|
|
- "Sync is beyond the blank.\n");
|
|
- return NULL;
|
|
- }
|
|
-
|
|
/* Some EDIDs have bogus h/vtotal values */
|
|
if (mode->hsync_end > mode->htotal)
|
|
mode->htotal = mode->hsync_end + 1;
|
|
if (mode->vsync_end > mode->vtotal)
|
|
mode->vtotal = mode->vsync_end + 1;
|
|
|
|
- drm_mode_set_name(mode);
|
|
-
|
|
drm_mode_do_interlace_quirk(mode, pt);
|
|
|
|
+ drm_mode_set_name(mode);
|
|
+
|
|
if (quirks & EDID_QUIRK_DETAILED_SYNC_PP) {
|
|
pt->misc |= DRM_EDID_PT_HSYNC_POSITIVE | DRM_EDID_PT_VSYNC_POSITIVE;
|
|
}
|
|
@@ -808,10 +1102,6 @@ static struct drm_display_mode edid_est_modes[] = {
|
|
DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 1152x864@75Hz */
|
|
};
|
|
|
|
-#define EDID_EST_TIMINGS 16
|
|
-#define EDID_STD_TIMINGS 8
|
|
-#define EDID_DETAILED_TIMINGS 4
|
|
-
|
|
/**
|
|
* add_established_modes - get est. modes from EDID and add them
|
|
* @edid: EDID block to scan
|
|
@@ -839,19 +1129,6 @@ static int add_established_modes(struct drm_connector *connector, struct edid *e
|
|
|
|
return modes;
|
|
}
|
|
-/**
|
|
- * stanard_timing_level - get std. timing level(CVT/GTF/DMT)
|
|
- * @edid: EDID block to scan
|
|
- */
|
|
-static int standard_timing_level(struct edid *edid)
|
|
-{
|
|
- if (edid->revision >= 2) {
|
|
- if (edid->revision >= 4 && (edid->features & DRM_EDID_FEATURE_DEFAULT_GTF))
|
|
- return LEVEL_CVT;
|
|
- return LEVEL_GTF;
|
|
- }
|
|
- return LEVEL_DMT;
|
|
-}
|
|
|
|
/**
|
|
* add_standard_modes - get std. modes from EDID and add them
|
|
@@ -862,22 +1139,14 @@ static int standard_timing_level(struct edid *edid)
|
|
*/
|
|
static int add_standard_modes(struct drm_connector *connector, struct edid *edid)
|
|
{
|
|
- struct drm_device *dev = connector->dev;
|
|
int i, modes = 0;
|
|
- int timing_level;
|
|
-
|
|
- timing_level = standard_timing_level(edid);
|
|
|
|
for (i = 0; i < EDID_STD_TIMINGS; i++) {
|
|
- struct std_timing *t = &edid->standard_timings[i];
|
|
struct drm_display_mode *newmode;
|
|
|
|
- /* If std timings bytes are 1, 1 it's empty */
|
|
- if (t->hsize == 1 && t->vfreq_aspect == 1)
|
|
- continue;
|
|
-
|
|
- newmode = drm_mode_std(dev, &edid->standard_timings[i],
|
|
- edid->revision, timing_level);
|
|
+ newmode = drm_mode_std(connector, edid,
|
|
+ &edid->standard_timings[i],
|
|
+ edid->revision);
|
|
if (newmode) {
|
|
drm_mode_probed_add(connector, newmode);
|
|
modes++;
|
|
@@ -887,36 +1156,86 @@ static int add_standard_modes(struct drm_connector *connector, struct edid *edid
|
|
return modes;
|
|
}
|
|
|
|
-/*
|
|
- * XXX fix this for:
|
|
- * - GTF secondary curve formula
|
|
- * - EDID 1.4 range offsets
|
|
- * - CVT extended bits
|
|
- */
|
|
static bool
|
|
-mode_in_range(struct drm_display_mode *mode, struct detailed_timing *timing)
|
|
+mode_is_rb(struct drm_display_mode *mode)
|
|
{
|
|
- struct detailed_data_monitor_range *range;
|
|
- int hsync, vrefresh;
|
|
-
|
|
- range = &timing->data.other_data.data.range;
|
|
+ return (mode->htotal - mode->hdisplay == 160) &&
|
|
+ (mode->hsync_end - mode->hdisplay == 80) &&
|
|
+ (mode->hsync_end - mode->hsync_start == 32) &&
|
|
+ (mode->vsync_start - mode->vdisplay == 3);
|
|
+}
|
|
|
|
+static bool
|
|
+mode_in_hsync_range(struct drm_display_mode *mode, struct edid *edid, u8 *t)
|
|
+{
|
|
+ int hsync, hmin, hmax;
|
|
+
|
|
+ hmin = t[7];
|
|
+ if (edid->revision >= 4)
|
|
+ hmin += ((t[4] & 0x04) ? 255 : 0);
|
|
+ hmax = t[8];
|
|
+ if (edid->revision >= 4)
|
|
+ hmax += ((t[4] & 0x08) ? 255 : 0);
|
|
hsync = drm_mode_hsync(mode);
|
|
- vrefresh = drm_mode_vrefresh(mode);
|
|
|
|
- if (hsync < range->min_hfreq_khz || hsync > range->max_hfreq_khz)
|
|
+ return (hsync <= hmax && hsync >= hmin);
|
|
+}
|
|
+
|
|
+static bool
|
|
+mode_in_vsync_range(struct drm_display_mode *mode, struct edid *edid, u8 *t)
|
|
+{
|
|
+ int vsync, vmin, vmax;
|
|
+
|
|
+ vmin = t[5];
|
|
+ if (edid->revision >= 4)
|
|
+ vmin += ((t[4] & 0x01) ? 255 : 0);
|
|
+ vmax = t[6];
|
|
+ if (edid->revision >= 4)
|
|
+ vmax += ((t[4] & 0x02) ? 255 : 0);
|
|
+ vsync = drm_mode_vrefresh(mode);
|
|
+
|
|
+ return (vsync <= vmax && vsync >= vmin);
|
|
+}
|
|
+
|
|
+static u32
|
|
+range_pixel_clock(struct edid *edid, u8 *t)
|
|
+{
|
|
+ /* unspecified */
|
|
+ if (t[9] == 0 || t[9] == 255)
|
|
+ return 0;
|
|
+
|
|
+ /* 1.4 with CVT support gives us real precision, yay */
|
|
+ if (edid->revision >= 4 && t[10] == 0x04)
|
|
+ return (t[9] * 10000) - ((t[12] >> 2) * 250);
|
|
+
|
|
+ /* 1.3 is pathetic, so fuzz up a bit */
|
|
+ return t[9] * 10000 + 5001;
|
|
+}
|
|
+
|
|
+static bool
|
|
+mode_in_range(struct drm_display_mode *mode, struct edid *edid,
|
|
+ struct detailed_timing *timing)
|
|
+{
|
|
+ u32 max_clock;
|
|
+ u8 *t = (u8 *)timing;
|
|
+
|
|
+ if (!mode_in_hsync_range(mode, edid, t))
|
|
return false;
|
|
|
|
- if (vrefresh < range->min_vfreq || vrefresh > range->max_vfreq)
|
|
+ if (!mode_in_vsync_range(mode, edid, t))
|
|
return false;
|
|
|
|
- if (range->pixel_clock_mhz && range->pixel_clock_mhz != 0xff) {
|
|
- /* be forgiving since it's in units of 10MHz */
|
|
- int max_clock = range->pixel_clock_mhz * 10 + 9;
|
|
- max_clock *= 1000;
|
|
+ if ((max_clock = range_pixel_clock(edid, t)))
|
|
if (mode->clock > max_clock)
|
|
return false;
|
|
- }
|
|
+
|
|
+ /* 1.4 max horizontal check */
|
|
+ if (edid->revision >= 4 && t[10] == 0x04)
|
|
+ if (t[13] && mode->hdisplay > 8 * (t[13] + (256 * (t[12]&0x3))))
|
|
+ return false;
|
|
+
|
|
+ if (mode_is_rb(mode) && !drm_monitor_supports_rb(edid))
|
|
+ return false;
|
|
|
|
return true;
|
|
}
|
|
@@ -925,15 +1244,16 @@ mode_in_range(struct drm_display_mode *mode, struct detailed_timing *timing)
|
|
* XXX If drm_dmt_modes ever regrows the CVT-R modes (and it will) this will
|
|
* need to account for them.
|
|
*/
|
|
-static int drm_gtf_modes_for_range(struct drm_connector *connector,
|
|
- struct detailed_timing *timing)
|
|
+static int
|
|
+drm_gtf_modes_for_range(struct drm_connector *connector, struct edid *edid,
|
|
+ struct detailed_timing *timing)
|
|
{
|
|
int i, modes = 0;
|
|
struct drm_display_mode *newmode;
|
|
struct drm_device *dev = connector->dev;
|
|
|
|
for (i = 0; i < drm_num_dmt_modes; i++) {
|
|
- if (mode_in_range(drm_dmt_modes + i, timing)) {
|
|
+ if (mode_in_range(drm_dmt_modes + i, edid, timing)) {
|
|
newmode = drm_mode_duplicate(dev, &drm_dmt_modes[i]);
|
|
if (newmode) {
|
|
drm_mode_probed_add(connector, newmode);
|
|
@@ -994,13 +1314,100 @@ static int drm_cvt_modes(struct drm_connector *connector,
|
|
return modes;
|
|
}
|
|
|
|
+static const struct {
|
|
+ short w;
|
|
+ short h;
|
|
+ short r;
|
|
+ short rb;
|
|
+} est3_modes[] = {
|
|
+ /* byte 6 */
|
|
+ { 640, 350, 85, 0 },
|
|
+ { 640, 400, 85, 0 },
|
|
+ { 720, 400, 85, 0 },
|
|
+ { 640, 480, 85, 0 },
|
|
+ { 848, 480, 60, 0 },
|
|
+ { 800, 600, 85, 0 },
|
|
+ { 1024, 768, 85, 0 },
|
|
+ { 1152, 864, 75, 0 },
|
|
+ /* byte 7 */
|
|
+ { 1280, 768, 60, 1 },
|
|
+ { 1280, 768, 60, 0 },
|
|
+ { 1280, 768, 75, 0 },
|
|
+ { 1280, 768, 85, 0 },
|
|
+ { 1280, 960, 60, 0 },
|
|
+ { 1280, 960, 85, 0 },
|
|
+ { 1280, 1024, 60, 0 },
|
|
+ { 1280, 1024, 85, 0 },
|
|
+ /* byte 8 */
|
|
+ { 1360, 768, 60, 0 },
|
|
+ { 1440, 900, 60, 1 },
|
|
+ { 1440, 900, 60, 0 },
|
|
+ { 1440, 900, 75, 0 },
|
|
+ { 1440, 900, 85, 0 },
|
|
+ { 1400, 1050, 60, 1 },
|
|
+ { 1400, 1050, 60, 0 },
|
|
+ { 1400, 1050, 75, 0 },
|
|
+ /* byte 9 */
|
|
+ { 1400, 1050, 85, 0 },
|
|
+ { 1680, 1050, 60, 1 },
|
|
+ { 1680, 1050, 60, 0 },
|
|
+ { 1680, 1050, 75, 0 },
|
|
+ { 1680, 1050, 85, 0 },
|
|
+ { 1600, 1200, 60, 0 },
|
|
+ { 1600, 1200, 65, 0 },
|
|
+ { 1600, 1200, 70, 0 },
|
|
+ /* byte 10 */
|
|
+ { 1600, 1200, 75, 0 },
|
|
+ { 1600, 1200, 85, 0 },
|
|
+ { 1792, 1344, 60, 0 },
|
|
+ { 1792, 1344, 85, 0 },
|
|
+ { 1856, 1392, 60, 0 },
|
|
+ { 1856, 1392, 75, 0 },
|
|
+ { 1920, 1200, 60, 1 },
|
|
+ { 1920, 1200, 60, 0 },
|
|
+ /* byte 11 */
|
|
+ { 1920, 1200, 75, 0 },
|
|
+ { 1920, 1200, 85, 0 },
|
|
+ { 1920, 1440, 60, 0 },
|
|
+ { 1920, 1440, 75, 0 },
|
|
+};
|
|
+static const int num_est3_modes = sizeof(est3_modes) / sizeof(est3_modes[0]);
|
|
+
|
|
+static int
|
|
+drm_est3_modes(struct drm_connector *connector, struct detailed_timing *timing)
|
|
+{
|
|
+ int i, j, m, modes = 0;
|
|
+ struct drm_display_mode *mode;
|
|
+ u8 *est = ((u8 *)timing) + 5;
|
|
+
|
|
+ for (i = 0; i < 6; i++) {
|
|
+ for (j = 7; j > 0; j--) {
|
|
+ m = (i * 8) + (7 - j);
|
|
+ if (m > num_est3_modes)
|
|
+ break;
|
|
+ if (est[i] & (1 << j)) {
|
|
+ mode = drm_find_dmt(connector->dev,
|
|
+ est3_modes[m].w,
|
|
+ est3_modes[m].h,
|
|
+ est3_modes[m].r
|
|
+ /*, est3_modes[m].rb */);
|
|
+ if (mode) {
|
|
+ drm_mode_probed_add(connector, mode);
|
|
+ modes++;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return modes;
|
|
+}
|
|
+
|
|
static int add_detailed_modes(struct drm_connector *connector,
|
|
struct detailed_timing *timing,
|
|
struct edid *edid, u32 quirks, int preferred)
|
|
{
|
|
int i, modes = 0;
|
|
struct detailed_non_pixel *data = &timing->data.other_data;
|
|
- int timing_level = standard_timing_level(edid);
|
|
int gtf = (edid->features & DRM_EDID_FEATURE_DEFAULT_GTF);
|
|
struct drm_display_mode *newmode;
|
|
struct drm_device *dev = connector->dev;
|
|
@@ -1021,7 +1428,8 @@ static int add_detailed_modes(struct drm_connector *connector,
|
|
switch (data->type) {
|
|
case EDID_DETAIL_MONITOR_RANGE:
|
|
if (gtf)
|
|
- modes += drm_gtf_modes_for_range(connector, timing);
|
|
+ modes += drm_gtf_modes_for_range(connector, edid,
|
|
+ timing);
|
|
break;
|
|
case EDID_DETAIL_STD_MODES:
|
|
/* Six modes per detailed section */
|
|
@@ -1030,8 +1438,8 @@ static int add_detailed_modes(struct drm_connector *connector,
|
|
struct drm_display_mode *newmode;
|
|
|
|
std = &data->data.timings[i];
|
|
- newmode = drm_mode_std(dev, std, edid->revision,
|
|
- timing_level);
|
|
+ newmode = drm_mode_std(connector, edid, std,
|
|
+ edid->revision);
|
|
if (newmode) {
|
|
drm_mode_probed_add(connector, newmode);
|
|
modes++;
|
|
@@ -1041,6 +1449,9 @@ static int add_detailed_modes(struct drm_connector *connector,
|
|
case EDID_DETAIL_CVT_3BYTE:
|
|
modes += drm_cvt_modes(connector, timing);
|
|
break;
|
|
+ case EDID_DETAIL_EST_TIMINGS:
|
|
+ modes += drm_est3_modes(connector, timing);
|
|
+ break;
|
|
default:
|
|
break;
|
|
}
|
|
@@ -1064,7 +1475,10 @@ static int add_detailed_info(struct drm_connector *connector,
|
|
|
|
for (i = 0; i < EDID_DETAILED_TIMINGS; i++) {
|
|
struct detailed_timing *timing = &edid->detailed_timings[i];
|
|
- int preferred = (i == 0) && (edid->features & DRM_EDID_FEATURE_PREFERRED_TIMING);
|
|
+ int preferred = (i == 0);
|
|
+
|
|
+ if (preferred && edid->version == 1 && edid->revision < 4)
|
|
+ preferred = (edid->features & DRM_EDID_FEATURE_PREFERRED_TIMING);
|
|
|
|
/* In 1.0, only timings are allowed */
|
|
if (!timing->pixel_clock && edid->version == 1 &&
|
|
@@ -1094,39 +1508,23 @@ static int add_detailed_info_eedid(struct drm_connector *connector,
|
|
int i, modes = 0;
|
|
char *edid_ext = NULL;
|
|
struct detailed_timing *timing;
|
|
- int edid_ext_num;
|
|
int start_offset, end_offset;
|
|
int timing_level;
|
|
|
|
- if (edid->version == 1 && edid->revision < 3) {
|
|
- /* If the EDID version is less than 1.3, there is no
|
|
- * extension EDID.
|
|
- */
|
|
+ if (edid->version == 1 && edid->revision < 3)
|
|
return 0;
|
|
- }
|
|
- if (!edid->extensions) {
|
|
- /* if there is no extension EDID, it is unnecessary to
|
|
- * parse the E-EDID to get detailed info
|
|
- */
|
|
+ if (!edid->extensions)
|
|
return 0;
|
|
- }
|
|
-
|
|
- /* Chose real EDID extension number */
|
|
- edid_ext_num = edid->extensions > MAX_EDID_EXT_NUM ?
|
|
- MAX_EDID_EXT_NUM : edid->extensions;
|
|
|
|
/* Find CEA extension */
|
|
- for (i = 0; i < edid_ext_num; i++) {
|
|
+ for (i = 0; i < edid->extensions; i++) {
|
|
edid_ext = (char *)edid + EDID_LENGTH * (i + 1);
|
|
- /* This block is CEA extension */
|
|
if (edid_ext[0] == 0x02)
|
|
break;
|
|
}
|
|
|
|
- if (i == edid_ext_num) {
|
|
- /* if there is no additional timing EDID block, return */
|
|
+ if (i == edid->extensions)
|
|
return 0;
|
|
- }
|
|
|
|
/* Get the start offset of detailed timing block */
|
|
start_offset = edid_ext[2];
|
|
@@ -1150,123 +1548,6 @@ static int add_detailed_info_eedid(struct drm_connector *connector,
|
|
return modes;
|
|
}
|
|
|
|
-#define DDC_ADDR 0x50
|
|
-/**
|
|
- * Get EDID information via I2C.
|
|
- *
|
|
- * \param adapter : i2c device adaptor
|
|
- * \param buf : EDID data buffer to be filled
|
|
- * \param len : EDID data buffer length
|
|
- * \return 0 on success or -1 on failure.
|
|
- *
|
|
- * Try to fetch EDID information by calling i2c driver function.
|
|
- */
|
|
-int drm_do_probe_ddc_edid(struct i2c_adapter *adapter,
|
|
- unsigned char *buf, int len)
|
|
-{
|
|
- unsigned char start = 0x0;
|
|
- struct i2c_msg msgs[] = {
|
|
- {
|
|
- .addr = DDC_ADDR,
|
|
- .flags = 0,
|
|
- .len = 1,
|
|
- .buf = &start,
|
|
- }, {
|
|
- .addr = DDC_ADDR,
|
|
- .flags = I2C_M_RD,
|
|
- .len = len,
|
|
- .buf = buf,
|
|
- }
|
|
- };
|
|
-
|
|
- if (i2c_transfer(adapter, msgs, 2) == 2)
|
|
- return 0;
|
|
-
|
|
- return -1;
|
|
-}
|
|
-EXPORT_SYMBOL(drm_do_probe_ddc_edid);
|
|
-
|
|
-static int drm_ddc_read_edid(struct drm_connector *connector,
|
|
- struct i2c_adapter *adapter,
|
|
- char *buf, int len)
|
|
-{
|
|
- int i;
|
|
-
|
|
- for (i = 0; i < 4; i++) {
|
|
- if (drm_do_probe_ddc_edid(adapter, buf, len))
|
|
- return -1;
|
|
- if (edid_is_valid((struct edid *)buf))
|
|
- return 0;
|
|
- }
|
|
-
|
|
- /* repeated checksum failures; warn, but carry on */
|
|
- dev_warn(&connector->dev->pdev->dev, "%s: EDID invalid.\n",
|
|
- drm_get_connector_name(connector));
|
|
- return -1;
|
|
-}
|
|
-
|
|
-/**
|
|
- * drm_get_edid - get EDID data, if available
|
|
- * @connector: connector we're probing
|
|
- * @adapter: i2c adapter to use for DDC
|
|
- *
|
|
- * Poke the given connector's i2c channel to grab EDID data if possible.
|
|
- *
|
|
- * Return edid data or NULL if we couldn't find any.
|
|
- */
|
|
-struct edid *drm_get_edid(struct drm_connector *connector,
|
|
- struct i2c_adapter *adapter)
|
|
-{
|
|
- int ret;
|
|
- struct edid *edid;
|
|
-
|
|
- edid = kmalloc(EDID_LENGTH * (MAX_EDID_EXT_NUM + 1),
|
|
- GFP_KERNEL);
|
|
- if (edid == NULL) {
|
|
- dev_warn(&connector->dev->pdev->dev,
|
|
- "Failed to allocate EDID\n");
|
|
- goto end;
|
|
- }
|
|
-
|
|
- /* Read first EDID block */
|
|
- ret = drm_ddc_read_edid(connector, adapter,
|
|
- (unsigned char *)edid, EDID_LENGTH);
|
|
- if (ret != 0)
|
|
- goto clean_up;
|
|
-
|
|
- /* There are EDID extensions to be read */
|
|
- if (edid->extensions != 0) {
|
|
- int edid_ext_num = edid->extensions;
|
|
-
|
|
- if (edid_ext_num > MAX_EDID_EXT_NUM) {
|
|
- dev_warn(&connector->dev->pdev->dev,
|
|
- "The number of extension(%d) is "
|
|
- "over max (%d), actually read number (%d)\n",
|
|
- edid_ext_num, MAX_EDID_EXT_NUM,
|
|
- MAX_EDID_EXT_NUM);
|
|
- /* Reset EDID extension number to be read */
|
|
- edid_ext_num = MAX_EDID_EXT_NUM;
|
|
- }
|
|
- /* Read EDID including extensions too */
|
|
- ret = drm_ddc_read_edid(connector, adapter, (char *)edid,
|
|
- EDID_LENGTH * (edid_ext_num + 1));
|
|
- if (ret != 0)
|
|
- goto clean_up;
|
|
-
|
|
- }
|
|
-
|
|
- connector->display_info.raw_edid = (char *)edid;
|
|
- goto end;
|
|
-
|
|
-clean_up:
|
|
- kfree(edid);
|
|
- edid = NULL;
|
|
-end:
|
|
- return edid;
|
|
-
|
|
-}
|
|
-EXPORT_SYMBOL(drm_get_edid);
|
|
-
|
|
#define HDMI_IDENTIFIER 0x000C03
|
|
#define VENDOR_BLOCK 0x03
|
|
/**
|
|
@@ -1279,7 +1560,7 @@ EXPORT_SYMBOL(drm_get_edid);
|
|
bool drm_detect_hdmi_monitor(struct edid *edid)
|
|
{
|
|
char *edid_ext = NULL;
|
|
- int i, hdmi_id, edid_ext_num;
|
|
+ int i, hdmi_id;
|
|
int start_offset, end_offset;
|
|
bool is_hdmi = false;
|
|
|
|
@@ -1287,19 +1568,15 @@ bool drm_detect_hdmi_monitor(struct edid *edid)
|
|
if (edid == NULL || edid->extensions == 0)
|
|
goto end;
|
|
|
|
- /* Chose real EDID extension number */
|
|
- edid_ext_num = edid->extensions > MAX_EDID_EXT_NUM ?
|
|
- MAX_EDID_EXT_NUM : edid->extensions;
|
|
-
|
|
/* Find CEA extension */
|
|
- for (i = 0; i < edid_ext_num; i++) {
|
|
+ for (i = 0; i < edid->extensions; i++) {
|
|
edid_ext = (char *)edid + EDID_LENGTH * (i + 1);
|
|
/* This block is CEA extension */
|
|
if (edid_ext[0] == 0x02)
|
|
break;
|
|
}
|
|
|
|
- if (i == edid_ext_num)
|
|
+ if (i == edid->extensions)
|
|
goto end;
|
|
|
|
/* Data block offset in CEA extension block */
|
|
@@ -1346,7 +1623,7 @@ int drm_add_edid_modes(struct drm_connector *connector, struct edid *edid)
|
|
if (edid == NULL) {
|
|
return 0;
|
|
}
|
|
- if (!edid_is_valid(edid)) {
|
|
+ if (!drm_edid_is_valid(edid)) {
|
|
dev_warn(&connector->dev->pdev->dev, "%s: EDID invalid.\n",
|
|
drm_get_connector_name(connector));
|
|
return 0;
|
|
@@ -1354,10 +1631,24 @@ int drm_add_edid_modes(struct drm_connector *connector, struct edid *edid)
|
|
|
|
quirks = edid_get_quirks(edid);
|
|
|
|
- num_modes += add_established_modes(connector, edid);
|
|
- num_modes += add_standard_modes(connector, edid);
|
|
+ /*
|
|
+ * EDID spec says modes should be preferred in this order:
|
|
+ * - preferred detailed mode
|
|
+ * - other detailed modes from base block
|
|
+ * - detailed modes from extension blocks
|
|
+ * - CVT 3-byte code modes
|
|
+ * - standard timing codes
|
|
+ * - established timing codes
|
|
+ * - modes inferred from GTF or CVT range information
|
|
+ *
|
|
+ * We don't quite implement this yet, but we're close.
|
|
+ *
|
|
+ * XXX order for additional mode types in extension blocks?
|
|
+ */
|
|
num_modes += add_detailed_info(connector, edid, quirks);
|
|
num_modes += add_detailed_info_eedid(connector, edid, quirks);
|
|
+ num_modes += add_standard_modes(connector, edid);
|
|
+ num_modes += add_established_modes(connector, edid);
|
|
|
|
if (quirks & (EDID_QUIRK_PREFER_LARGE_60 | EDID_QUIRK_PREFER_LARGE_75))
|
|
edid_fixup_preferred(connector, quirks);
|
|
diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
|
|
index 0f9e905..da258ca 100644
|
|
--- a/drivers/gpu/drm/drm_fb_helper.c
|
|
+++ b/drivers/gpu/drm/drm_fb_helper.c
|
|
@@ -27,6 +27,7 @@
|
|
* Dave Airlie <airlied@linux.ie>
|
|
* Jesse Barnes <jesse.barnes@intel.com>
|
|
*/
|
|
+#include <linux/kernel.h>
|
|
#include <linux/sysrq.h>
|
|
#include <linux/fb.h>
|
|
#include "drmP.h"
|
|
@@ -50,21 +51,6 @@ int drm_fb_helper_add_connector(struct drm_connector *connector)
|
|
}
|
|
EXPORT_SYMBOL(drm_fb_helper_add_connector);
|
|
|
|
-static int my_atoi(const char *name)
|
|
-{
|
|
- int val = 0;
|
|
-
|
|
- for (;; name++) {
|
|
- switch (*name) {
|
|
- case '0' ... '9':
|
|
- val = 10*val+(*name-'0');
|
|
- break;
|
|
- default:
|
|
- return val;
|
|
- }
|
|
- }
|
|
-}
|
|
-
|
|
/**
|
|
* drm_fb_helper_connector_parse_command_line - parse command line for connector
|
|
* @connector - connector to parse line for
|
|
@@ -111,7 +97,7 @@ static bool drm_fb_helper_connector_parse_command_line(struct drm_connector *con
|
|
namelen = i;
|
|
if (!refresh_specified && !bpp_specified &&
|
|
!yres_specified) {
|
|
- refresh = my_atoi(&name[i+1]);
|
|
+ refresh = simple_strtol(&name[i+1], NULL, 10);
|
|
refresh_specified = 1;
|
|
if (cvt || rb)
|
|
cvt = 0;
|
|
@@ -121,7 +107,7 @@ static bool drm_fb_helper_connector_parse_command_line(struct drm_connector *con
|
|
case '-':
|
|
namelen = i;
|
|
if (!bpp_specified && !yres_specified) {
|
|
- bpp = my_atoi(&name[i+1]);
|
|
+ bpp = simple_strtol(&name[i+1], NULL, 10);
|
|
bpp_specified = 1;
|
|
if (cvt || rb)
|
|
cvt = 0;
|
|
@@ -130,7 +116,7 @@ static bool drm_fb_helper_connector_parse_command_line(struct drm_connector *con
|
|
break;
|
|
case 'x':
|
|
if (!yres_specified) {
|
|
- yres = my_atoi(&name[i+1]);
|
|
+ yres = simple_strtol(&name[i+1], NULL, 10);
|
|
yres_specified = 1;
|
|
} else
|
|
goto done;
|
|
@@ -170,7 +156,7 @@ static bool drm_fb_helper_connector_parse_command_line(struct drm_connector *con
|
|
}
|
|
}
|
|
if (i < 0 && yres_specified) {
|
|
- xres = my_atoi(name);
|
|
+ xres = simple_strtol(name, NULL, 10);
|
|
res_specified = 1;
|
|
}
|
|
done:
|
|
@@ -297,6 +283,8 @@ static struct sysrq_key_op sysrq_drm_fb_helper_restore_op = {
|
|
.help_msg = "force-fb(V)",
|
|
.action_msg = "Restore framebuffer console",
|
|
};
|
|
+#else
|
|
+static struct sysrq_key_op sysrq_drm_fb_helper_restore_op = { };
|
|
#endif
|
|
|
|
static void drm_fb_helper_on(struct fb_info *info)
|
|
diff --git a/drivers/gpu/drm/drm_fops.c b/drivers/gpu/drm/drm_fops.c
|
|
index 08d14df..4804872 100644
|
|
--- a/drivers/gpu/drm/drm_fops.c
|
|
+++ b/drivers/gpu/drm/drm_fops.c
|
|
@@ -140,14 +140,16 @@ int drm_open(struct inode *inode, struct file *filp)
|
|
spin_unlock(&dev->count_lock);
|
|
}
|
|
out:
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- if (minor->type == DRM_MINOR_LEGACY) {
|
|
- BUG_ON((dev->dev_mapping != NULL) &&
|
|
- (dev->dev_mapping != inode->i_mapping));
|
|
- if (dev->dev_mapping == NULL)
|
|
- dev->dev_mapping = inode->i_mapping;
|
|
+ if (!retcode) {
|
|
+ mutex_lock(&dev->struct_mutex);
|
|
+ if (minor->type == DRM_MINOR_LEGACY) {
|
|
+ if (dev->dev_mapping == NULL)
|
|
+ dev->dev_mapping = inode->i_mapping;
|
|
+ else if (dev->dev_mapping != inode->i_mapping)
|
|
+ retcode = -ENODEV;
|
|
+ }
|
|
+ mutex_unlock(&dev->struct_mutex);
|
|
}
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
|
|
return retcode;
|
|
}
|
|
diff --git a/drivers/gpu/drm/drm_gem.c b/drivers/gpu/drm/drm_gem.c
|
|
index 8bf3770..aa89d4b 100644
|
|
--- a/drivers/gpu/drm/drm_gem.c
|
|
+++ b/drivers/gpu/drm/drm_gem.c
|
|
@@ -192,9 +192,7 @@ drm_gem_handle_delete(struct drm_file *filp, u32 handle)
|
|
idr_remove(&filp->object_idr, handle);
|
|
spin_unlock(&filp->table_lock);
|
|
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_handle_unreference(obj);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
+ drm_gem_object_handle_unreference_unlocked(obj);
|
|
|
|
return 0;
|
|
}
|
|
@@ -325,9 +323,7 @@ again:
|
|
}
|
|
|
|
err:
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_unreference(obj);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
+ drm_gem_object_unreference_unlocked(obj);
|
|
return ret;
|
|
}
|
|
|
|
@@ -358,9 +354,7 @@ drm_gem_open_ioctl(struct drm_device *dev, void *data,
|
|
return -ENOENT;
|
|
|
|
ret = drm_gem_handle_create(file_priv, obj, &handle);
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_unreference(obj);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
+ drm_gem_object_unreference_unlocked(obj);
|
|
if (ret)
|
|
return ret;
|
|
|
|
@@ -390,7 +384,7 @@ drm_gem_object_release_handle(int id, void *ptr, void *data)
|
|
{
|
|
struct drm_gem_object *obj = ptr;
|
|
|
|
- drm_gem_object_handle_unreference(obj);
|
|
+ drm_gem_object_handle_unreference_unlocked(obj);
|
|
|
|
return 0;
|
|
}
|
|
@@ -403,16 +397,25 @@ drm_gem_object_release_handle(int id, void *ptr, void *data)
|
|
void
|
|
drm_gem_release(struct drm_device *dev, struct drm_file *file_private)
|
|
{
|
|
- mutex_lock(&dev->struct_mutex);
|
|
idr_for_each(&file_private->object_idr,
|
|
&drm_gem_object_release_handle, NULL);
|
|
|
|
idr_destroy(&file_private->object_idr);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
+}
|
|
+
|
|
+static void
|
|
+drm_gem_object_free_common(struct drm_gem_object *obj)
|
|
+{
|
|
+ struct drm_device *dev = obj->dev;
|
|
+ fput(obj->filp);
|
|
+ atomic_dec(&dev->object_count);
|
|
+ atomic_sub(obj->size, &dev->object_memory);
|
|
+ kfree(obj);
|
|
}
|
|
|
|
/**
|
|
* Called after the last reference to the object has been lost.
|
|
+ * Must be called holding struct_ mutex
|
|
*
|
|
* Frees the object
|
|
*/
|
|
@@ -427,14 +430,40 @@ drm_gem_object_free(struct kref *kref)
|
|
if (dev->driver->gem_free_object != NULL)
|
|
dev->driver->gem_free_object(obj);
|
|
|
|
- fput(obj->filp);
|
|
- atomic_dec(&dev->object_count);
|
|
- atomic_sub(obj->size, &dev->object_memory);
|
|
- kfree(obj);
|
|
+ drm_gem_object_free_common(obj);
|
|
}
|
|
EXPORT_SYMBOL(drm_gem_object_free);
|
|
|
|
/**
|
|
+ * Called after the last reference to the object has been lost.
|
|
+ * Must be called without holding struct_mutex
|
|
+ *
|
|
+ * Frees the object
|
|
+ */
|
|
+void
|
|
+drm_gem_object_free_unlocked(struct kref *kref)
|
|
+{
|
|
+ struct drm_gem_object *obj = (struct drm_gem_object *) kref;
|
|
+ struct drm_device *dev = obj->dev;
|
|
+
|
|
+ if (dev->driver->gem_free_object_unlocked != NULL)
|
|
+ dev->driver->gem_free_object_unlocked(obj);
|
|
+ else if (dev->driver->gem_free_object != NULL) {
|
|
+ mutex_lock(&dev->struct_mutex);
|
|
+ dev->driver->gem_free_object(obj);
|
|
+ mutex_unlock(&dev->struct_mutex);
|
|
+ }
|
|
+
|
|
+ drm_gem_object_free_common(obj);
|
|
+}
|
|
+EXPORT_SYMBOL(drm_gem_object_free_unlocked);
|
|
+
|
|
+static void drm_gem_object_ref_bug(struct kref *list_kref)
|
|
+{
|
|
+ BUG();
|
|
+}
|
|
+
|
|
+/**
|
|
* Called after the last handle to the object has been closed
|
|
*
|
|
* Removes any name for the object. Note that this must be
|
|
@@ -458,8 +487,10 @@ drm_gem_object_handle_free(struct kref *kref)
|
|
/*
|
|
* The object name held a reference to this object, drop
|
|
* that now.
|
|
+ *
|
|
+ * This cannot be the last reference, since the handle holds one too.
|
|
*/
|
|
- drm_gem_object_unreference(obj);
|
|
+ kref_put(&obj->refcount, drm_gem_object_ref_bug);
|
|
} else
|
|
spin_unlock(&dev->object_name_lock);
|
|
|
|
@@ -477,11 +508,8 @@ EXPORT_SYMBOL(drm_gem_vm_open);
|
|
void drm_gem_vm_close(struct vm_area_struct *vma)
|
|
{
|
|
struct drm_gem_object *obj = vma->vm_private_data;
|
|
- struct drm_device *dev = obj->dev;
|
|
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_unreference(obj);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
+ drm_gem_object_unreference_unlocked(obj);
|
|
}
|
|
EXPORT_SYMBOL(drm_gem_vm_close);
|
|
|
|
diff --git a/drivers/gpu/drm/drm_modes.c b/drivers/gpu/drm/drm_modes.c
|
|
index 76d6339..f1f473e 100644
|
|
--- a/drivers/gpu/drm/drm_modes.c
|
|
+++ b/drivers/gpu/drm/drm_modes.c
|
|
@@ -258,8 +258,10 @@ struct drm_display_mode *drm_cvt_mode(struct drm_device *dev, int hdisplay,
|
|
drm_mode->clock -= drm_mode->clock % CVT_CLOCK_STEP;
|
|
/* 18/16. Find actual vertical frame frequency */
|
|
/* ignore - just set the mode flag for interlaced */
|
|
- if (interlaced)
|
|
+ if (interlaced) {
|
|
drm_mode->vtotal *= 2;
|
|
+ drm_mode->flags |= DRM_MODE_FLAG_INTERLACE;
|
|
+ }
|
|
/* Fill the mode line name */
|
|
drm_mode_set_name(drm_mode);
|
|
if (reduced)
|
|
@@ -268,43 +270,35 @@ struct drm_display_mode *drm_cvt_mode(struct drm_device *dev, int hdisplay,
|
|
else
|
|
drm_mode->flags |= (DRM_MODE_FLAG_PVSYNC |
|
|
DRM_MODE_FLAG_NHSYNC);
|
|
- if (interlaced)
|
|
- drm_mode->flags |= DRM_MODE_FLAG_INTERLACE;
|
|
|
|
- return drm_mode;
|
|
+ return drm_mode;
|
|
}
|
|
EXPORT_SYMBOL(drm_cvt_mode);
|
|
|
|
/**
|
|
- * drm_gtf_mode - create the modeline based on GTF algorithm
|
|
+ * drm_gtf_mode_complex - create the modeline based on full GTF algorithm
|
|
*
|
|
* @dev :drm device
|
|
* @hdisplay :hdisplay size
|
|
* @vdisplay :vdisplay size
|
|
* @vrefresh :vrefresh rate.
|
|
* @interlaced :whether the interlace is supported
|
|
- * @margins :whether the margin is supported
|
|
+ * @margins :desired margin size
|
|
+ * @GTF_[MCKJ] :extended GTF formula parameters
|
|
*
|
|
* LOCKING.
|
|
* none.
|
|
*
|
|
- * return the modeline based on GTF algorithm
|
|
- *
|
|
- * This function is to create the modeline based on the GTF algorithm.
|
|
- * Generalized Timing Formula is derived from:
|
|
- * GTF Spreadsheet by Andy Morrish (1/5/97)
|
|
- * available at http://www.vesa.org
|
|
+ * return the modeline based on full GTF algorithm.
|
|
*
|
|
- * And it is copied from the file of xserver/hw/xfree86/modes/xf86gtf.c.
|
|
- * What I have done is to translate it by using integer calculation.
|
|
- * I also refer to the function of fb_get_mode in the file of
|
|
- * drivers/video/fbmon.c
|
|
+ * GTF feature blocks specify C and J in multiples of 0.5, so we pass them
|
|
+ * in here multiplied by two. For a C of 40, pass in 80.
|
|
*/
|
|
-struct drm_display_mode *drm_gtf_mode(struct drm_device *dev, int hdisplay,
|
|
- int vdisplay, int vrefresh,
|
|
- bool interlaced, int margins)
|
|
-{
|
|
- /* 1) top/bottom margin size (% of height) - default: 1.8, */
|
|
+struct drm_display_mode *
|
|
+drm_gtf_mode_complex(struct drm_device *dev, int hdisplay, int vdisplay,
|
|
+ int vrefresh, bool interlaced, int margins,
|
|
+ int GTF_M, int GTF_2C, int GTF_K, int GTF_2J)
|
|
+{ /* 1) top/bottom margin size (% of height) - default: 1.8, */
|
|
#define GTF_MARGIN_PERCENTAGE 18
|
|
/* 2) character cell horizontal granularity (pixels) - default 8 */
|
|
#define GTF_CELL_GRAN 8
|
|
@@ -316,17 +310,9 @@ struct drm_display_mode *drm_gtf_mode(struct drm_device *dev, int hdisplay,
|
|
#define H_SYNC_PERCENT 8
|
|
/* min time of vsync + back porch (microsec) */
|
|
#define MIN_VSYNC_PLUS_BP 550
|
|
- /* blanking formula gradient */
|
|
-#define GTF_M 600
|
|
- /* blanking formula offset */
|
|
-#define GTF_C 40
|
|
- /* blanking formula scaling factor */
|
|
-#define GTF_K 128
|
|
- /* blanking formula scaling factor */
|
|
-#define GTF_J 20
|
|
/* C' and M' are part of the Blanking Duty Cycle computation */
|
|
-#define GTF_C_PRIME (((GTF_C - GTF_J) * GTF_K / 256) + GTF_J)
|
|
-#define GTF_M_PRIME (GTF_K * GTF_M / 256)
|
|
+#define GTF_C_PRIME ((((GTF_2C - GTF_2J) * GTF_K / 256) + GTF_2J) / 2)
|
|
+#define GTF_M_PRIME (GTF_K * GTF_M / 256)
|
|
struct drm_display_mode *drm_mode;
|
|
unsigned int hdisplay_rnd, vdisplay_rnd, vfieldrate_rqd;
|
|
int top_margin, bottom_margin;
|
|
@@ -460,17 +446,61 @@ struct drm_display_mode *drm_gtf_mode(struct drm_device *dev, int hdisplay,
|
|
|
|
drm_mode->clock = pixel_freq;
|
|
|
|
- drm_mode_set_name(drm_mode);
|
|
- drm_mode->flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC;
|
|
-
|
|
if (interlaced) {
|
|
drm_mode->vtotal *= 2;
|
|
drm_mode->flags |= DRM_MODE_FLAG_INTERLACE;
|
|
}
|
|
|
|
+ drm_mode_set_name(drm_mode);
|
|
+ if (GTF_M == 600 && GTF_2C == 80 && GTF_K == 128 && GTF_2J == 40)
|
|
+ drm_mode->flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC;
|
|
+ else
|
|
+ drm_mode->flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC;
|
|
+
|
|
return drm_mode;
|
|
}
|
|
+EXPORT_SYMBOL(drm_gtf_mode_complex);
|
|
+
|
|
+/**
|
|
+ * drm_gtf_mode - create the modeline based on GTF algorithm
|
|
+ *
|
|
+ * @dev :drm device
|
|
+ * @hdisplay :hdisplay size
|
|
+ * @vdisplay :vdisplay size
|
|
+ * @vrefresh :vrefresh rate.
|
|
+ * @interlaced :whether the interlace is supported
|
|
+ * @margins :whether the margin is supported
|
|
+ *
|
|
+ * LOCKING.
|
|
+ * none.
|
|
+ *
|
|
+ * return the modeline based on GTF algorithm
|
|
+ *
|
|
+ * This function is to create the modeline based on the GTF algorithm.
|
|
+ * Generalized Timing Formula is derived from:
|
|
+ * GTF Spreadsheet by Andy Morrish (1/5/97)
|
|
+ * available at http://www.vesa.org
|
|
+ *
|
|
+ * And it is copied from the file of xserver/hw/xfree86/modes/xf86gtf.c.
|
|
+ * What I have done is to translate it by using integer calculation.
|
|
+ * I also refer to the function of fb_get_mode in the file of
|
|
+ * drivers/video/fbmon.c
|
|
+ *
|
|
+ * Standard GTF parameters:
|
|
+ * M = 600
|
|
+ * C = 40
|
|
+ * K = 128
|
|
+ * J = 20
|
|
+ */
|
|
+struct drm_display_mode *
|
|
+drm_gtf_mode(struct drm_device *dev, int hdisplay, int vdisplay, int vrefresh,
|
|
+ bool lace, int margins)
|
|
+{
|
|
+ return drm_gtf_mode_complex(dev, hdisplay, vdisplay, vrefresh, lace,
|
|
+ margins, 600, 40 * 2, 128, 20 * 2);
|
|
+}
|
|
EXPORT_SYMBOL(drm_gtf_mode);
|
|
+
|
|
/**
|
|
* drm_mode_set_name - set the name on a mode
|
|
* @mode: name will be set in this mode
|
|
@@ -482,8 +512,11 @@ EXPORT_SYMBOL(drm_gtf_mode);
|
|
*/
|
|
void drm_mode_set_name(struct drm_display_mode *mode)
|
|
{
|
|
- snprintf(mode->name, DRM_DISPLAY_MODE_LEN, "%dx%d", mode->hdisplay,
|
|
- mode->vdisplay);
|
|
+ bool interlaced = !!(mode->flags & DRM_MODE_FLAG_INTERLACE);
|
|
+
|
|
+ snprintf(mode->name, DRM_DISPLAY_MODE_LEN, "%dx%d%s",
|
|
+ mode->hdisplay, mode->vdisplay,
|
|
+ interlaced ? "i" : "");
|
|
}
|
|
EXPORT_SYMBOL(drm_mode_set_name);
|
|
|
|
diff --git a/drivers/gpu/drm/drm_sysfs.c b/drivers/gpu/drm/drm_sysfs.c
|
|
index 7e42b7e..9721513 100644
|
|
--- a/drivers/gpu/drm/drm_sysfs.c
|
|
+++ b/drivers/gpu/drm/drm_sysfs.c
|
|
@@ -334,7 +334,7 @@ static struct device_attribute connector_attrs_opt1[] = {
|
|
static struct bin_attribute edid_attr = {
|
|
.attr.name = "edid",
|
|
.attr.mode = 0444,
|
|
- .size = 128,
|
|
+ .size = 0,
|
|
.read = edid_show,
|
|
};
|
|
|
|
diff --git a/drivers/gpu/drm/i915/i915_gem.c b/drivers/gpu/drm/i915/i915_gem.c
|
|
index fd099a1..1c0cc9e 100644
|
|
--- a/drivers/gpu/drm/i915/i915_gem.c
|
|
+++ b/drivers/gpu/drm/i915/i915_gem.c
|
|
@@ -128,9 +128,7 @@ i915_gem_create_ioctl(struct drm_device *dev, void *data,
|
|
return -ENOMEM;
|
|
|
|
ret = drm_gem_handle_create(file_priv, obj, &handle);
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_handle_unreference(obj);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
+ drm_gem_object_handle_unreference_unlocked(obj);
|
|
|
|
if (ret)
|
|
return ret;
|
|
@@ -488,7 +486,7 @@ i915_gem_pread_ioctl(struct drm_device *dev, void *data,
|
|
*/
|
|
if (args->offset > obj->size || args->size > obj->size ||
|
|
args->offset + args->size > obj->size) {
|
|
- drm_gem_object_unreference(obj);
|
|
+ drm_gem_object_unreference_unlocked(obj);
|
|
return -EINVAL;
|
|
}
|
|
|
|
@@ -501,7 +499,7 @@ i915_gem_pread_ioctl(struct drm_device *dev, void *data,
|
|
file_priv);
|
|
}
|
|
|
|
- drm_gem_object_unreference(obj);
|
|
+ drm_gem_object_unreference_unlocked(obj);
|
|
|
|
return ret;
|
|
}
|
|
@@ -961,7 +959,7 @@ i915_gem_pwrite_ioctl(struct drm_device *dev, void *data,
|
|
*/
|
|
if (args->offset > obj->size || args->size > obj->size ||
|
|
args->offset + args->size > obj->size) {
|
|
- drm_gem_object_unreference(obj);
|
|
+ drm_gem_object_unreference_unlocked(obj);
|
|
return -EINVAL;
|
|
}
|
|
|
|
@@ -995,7 +993,7 @@ i915_gem_pwrite_ioctl(struct drm_device *dev, void *data,
|
|
DRM_INFO("pwrite failed %d\n", ret);
|
|
#endif
|
|
|
|
- drm_gem_object_unreference(obj);
|
|
+ drm_gem_object_unreference_unlocked(obj);
|
|
|
|
return ret;
|
|
}
|
|
@@ -1138,9 +1136,7 @@ i915_gem_mmap_ioctl(struct drm_device *dev, void *data,
|
|
PROT_READ | PROT_WRITE, MAP_SHARED,
|
|
args->offset);
|
|
up_write(¤t->mm->mmap_sem);
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_unreference(obj);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
+ drm_gem_object_unreference_unlocked(obj);
|
|
if (IS_ERR((void *)addr))
|
|
return addr;
|
|
|
|
diff --git a/drivers/gpu/drm/i915/i915_gem_tiling.c b/drivers/gpu/drm/i915/i915_gem_tiling.c
|
|
index df278b2..137e888 100644
|
|
--- a/drivers/gpu/drm/i915/i915_gem_tiling.c
|
|
+++ b/drivers/gpu/drm/i915/i915_gem_tiling.c
|
|
@@ -438,9 +438,7 @@ i915_gem_set_tiling(struct drm_device *dev, void *data,
|
|
obj_priv = obj->driver_private;
|
|
|
|
if (!i915_tiling_ok(dev, args->stride, obj->size, args->tiling_mode)) {
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_unreference(obj);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
+ drm_gem_object_unreference_unlocked(obj);
|
|
return -EINVAL;
|
|
}
|
|
|
|
diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c
|
|
index b27202d..c8fd15f 100644
|
|
--- a/drivers/gpu/drm/i915/intel_display.c
|
|
+++ b/drivers/gpu/drm/i915/intel_display.c
|
|
@@ -3553,11 +3553,10 @@ static int intel_crtc_cursor_set(struct drm_crtc *crtc,
|
|
intel_crtc->cursor_bo = bo;
|
|
|
|
return 0;
|
|
-fail:
|
|
- mutex_lock(&dev->struct_mutex);
|
|
fail_locked:
|
|
- drm_gem_object_unreference(bo);
|
|
mutex_unlock(&dev->struct_mutex);
|
|
+fail:
|
|
+ drm_gem_object_unreference_unlocked(bo);
|
|
return ret;
|
|
}
|
|
|
|
@@ -4476,9 +4475,7 @@ static void intel_user_framebuffer_destroy(struct drm_framebuffer *fb)
|
|
intelfb_remove(dev, fb);
|
|
|
|
drm_framebuffer_cleanup(fb);
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_unreference(intel_fb->obj);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
+ drm_gem_object_unreference_unlocked(intel_fb->obj);
|
|
|
|
kfree(intel_fb);
|
|
}
|
|
@@ -4541,9 +4538,7 @@ intel_user_framebuffer_create(struct drm_device *dev,
|
|
|
|
ret = intel_framebuffer_create(dev, mode_cmd, &fb, obj);
|
|
if (ret) {
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_unreference(obj);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
+ drm_gem_object_unreference_unlocked(obj);
|
|
return NULL;
|
|
}
|
|
|
|
diff --git a/drivers/gpu/drm/i915/intel_overlay.c b/drivers/gpu/drm/i915/intel_overlay.c
|
|
index 63f569b..f8887f6 100644
|
|
--- a/drivers/gpu/drm/i915/intel_overlay.c
|
|
+++ b/drivers/gpu/drm/i915/intel_overlay.c
|
|
@@ -1183,8 +1183,8 @@ int intel_overlay_put_image(struct drm_device *dev, void *data,
|
|
out_unlock:
|
|
mutex_unlock(&dev->struct_mutex);
|
|
mutex_unlock(&dev->mode_config.mutex);
|
|
- drm_gem_object_unreference(new_bo);
|
|
out_free:
|
|
+ drm_gem_object_unreference_unlocked(new_bo);
|
|
kfree(params);
|
|
|
|
return ret;
|
|
diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c
|
|
index dfc9439..cf1c5c0 100644
|
|
--- a/drivers/gpu/drm/nouveau/nouveau_display.c
|
|
+++ b/drivers/gpu/drm/nouveau/nouveau_display.c
|
|
@@ -39,11 +39,8 @@ nouveau_user_framebuffer_destroy(struct drm_framebuffer *drm_fb)
|
|
if (drm_fb->fbdev)
|
|
nouveau_fbcon_remove(dev, drm_fb);
|
|
|
|
- if (fb->nvbo) {
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_unreference(fb->nvbo->gem);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
- }
|
|
+ if (fb->nvbo)
|
|
+ drm_gem_object_unreference_unlocked(fb->nvbo->gem);
|
|
|
|
drm_framebuffer_cleanup(drm_fb);
|
|
kfree(fb);
|
|
diff --git a/drivers/gpu/drm/nouveau/nouveau_fbcon.c b/drivers/gpu/drm/nouveau/nouveau_fbcon.c
|
|
index ea879a2..d48c59c 100644
|
|
--- a/drivers/gpu/drm/nouveau/nouveau_fbcon.c
|
|
+++ b/drivers/gpu/drm/nouveau/nouveau_fbcon.c
|
|
@@ -401,10 +401,8 @@ nouveau_fbcon_remove(struct drm_device *dev, struct drm_framebuffer *fb)
|
|
|
|
unregister_framebuffer(info);
|
|
nouveau_bo_unmap(nouveau_fb->nvbo);
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_unreference(nouveau_fb->nvbo->gem);
|
|
+ drm_gem_object_unreference_unlocked(nouveau_fb->nvbo->gem);
|
|
nouveau_fb->nvbo = NULL;
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
if (par)
|
|
drm_fb_helper_free(&par->helper);
|
|
framebuffer_release(info);
|
|
diff --git a/drivers/gpu/drm/nouveau/nouveau_gem.c b/drivers/gpu/drm/nouveau/nouveau_gem.c
|
|
index 70cc308..34063c5 100644
|
|
--- a/drivers/gpu/drm/nouveau/nouveau_gem.c
|
|
+++ b/drivers/gpu/drm/nouveau/nouveau_gem.c
|
|
@@ -167,12 +167,10 @@ nouveau_gem_ioctl_new(struct drm_device *dev, void *data,
|
|
|
|
ret = drm_gem_handle_create(file_priv, nvbo->gem, &req->info.handle);
|
|
out:
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_handle_unreference(nvbo->gem);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
+ drm_gem_object_handle_unreference_unlocked(nvbo->gem);
|
|
|
|
if (ret)
|
|
- drm_gem_object_unreference(nvbo->gem);
|
|
+ drm_gem_object_unreference_unlocked(nvbo->gem);
|
|
return ret;
|
|
}
|
|
|
|
@@ -865,9 +863,7 @@ nouveau_gem_ioctl_pin(struct drm_device *dev, void *data,
|
|
req->domain = NOUVEAU_GEM_DOMAIN_VRAM;
|
|
|
|
out:
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_unreference(gem);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
+ drm_gem_object_unreference_unlocked(gem);
|
|
|
|
return ret;
|
|
}
|
|
@@ -891,9 +887,7 @@ nouveau_gem_ioctl_unpin(struct drm_device *dev, void *data,
|
|
|
|
ret = nouveau_bo_unpin(nouveau_gem_object(gem));
|
|
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_unreference(gem);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
+ drm_gem_object_unreference_unlocked(gem);
|
|
|
|
return ret;
|
|
}
|
|
@@ -935,9 +929,7 @@ nouveau_gem_ioctl_cpu_prep(struct drm_device *dev, void *data,
|
|
}
|
|
|
|
out:
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_unreference(gem);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
+ drm_gem_object_unreference_unlocked(gem);
|
|
return ret;
|
|
}
|
|
|
|
@@ -965,9 +957,7 @@ nouveau_gem_ioctl_cpu_fini(struct drm_device *dev, void *data,
|
|
ret = 0;
|
|
|
|
out:
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_unreference(gem);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
+ drm_gem_object_unreference_unlocked(gem);
|
|
return ret;
|
|
}
|
|
|
|
@@ -986,9 +976,7 @@ nouveau_gem_ioctl_info(struct drm_device *dev, void *data,
|
|
return -EINVAL;
|
|
|
|
ret = nouveau_gem_info(gem, req);
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_unreference(gem);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
+ drm_gem_object_unreference_unlocked(gem);
|
|
return ret;
|
|
}
|
|
|
|
diff --git a/drivers/gpu/drm/nouveau/nouveau_notifier.c b/drivers/gpu/drm/nouveau/nouveau_notifier.c
|
|
index d99dc08..9537f3e 100644
|
|
--- a/drivers/gpu/drm/nouveau/nouveau_notifier.c
|
|
+++ b/drivers/gpu/drm/nouveau/nouveau_notifier.c
|
|
@@ -61,11 +61,8 @@ nouveau_notifier_init_channel(struct nouveau_channel *chan)
|
|
|
|
chan->notifier_bo = ntfy;
|
|
out_err:
|
|
- if (ret) {
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_unreference(ntfy->gem);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
- }
|
|
+ if (ret)
|
|
+ drm_gem_object_unreference_unlocked(ntfy->gem);
|
|
|
|
return ret;
|
|
}
|
|
@@ -81,8 +78,8 @@ nouveau_notifier_takedown_channel(struct nouveau_channel *chan)
|
|
nouveau_bo_unmap(chan->notifier_bo);
|
|
mutex_lock(&dev->struct_mutex);
|
|
nouveau_bo_unpin(chan->notifier_bo);
|
|
- drm_gem_object_unreference(chan->notifier_bo->gem);
|
|
mutex_unlock(&dev->struct_mutex);
|
|
+ drm_gem_object_unreference_unlocked(chan->notifier_bo->gem);
|
|
nouveau_mem_takedown(&chan->notifier_heap);
|
|
}
|
|
|
|
diff --git a/drivers/gpu/drm/nouveau/nv04_crtc.c b/drivers/gpu/drm/nouveau/nv04_crtc.c
|
|
index d2f143e..a1d1ebb 100644
|
|
--- a/drivers/gpu/drm/nouveau/nv04_crtc.c
|
|
+++ b/drivers/gpu/drm/nouveau/nv04_crtc.c
|
|
@@ -926,9 +926,7 @@ nv04_crtc_cursor_set(struct drm_crtc *crtc, struct drm_file *file_priv,
|
|
nv_crtc->cursor.set_offset(nv_crtc, nv_crtc->cursor.offset);
|
|
nv_crtc->cursor.show(nv_crtc, true);
|
|
out:
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_unreference(gem);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
+ drm_gem_object_unreference_unlocked(gem);
|
|
return ret;
|
|
}
|
|
|
|
diff --git a/drivers/gpu/drm/nouveau/nv50_crtc.c b/drivers/gpu/drm/nouveau/nv50_crtc.c
|
|
index d1a651e..cfabeb9 100644
|
|
--- a/drivers/gpu/drm/nouveau/nv50_crtc.c
|
|
+++ b/drivers/gpu/drm/nouveau/nv50_crtc.c
|
|
@@ -358,9 +358,7 @@ nv50_crtc_cursor_set(struct drm_crtc *crtc, struct drm_file *file_priv,
|
|
nv_crtc->cursor.show(nv_crtc, true);
|
|
|
|
out:
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_unreference(gem);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
+ drm_gem_object_unreference_unlocked(gem);
|
|
return ret;
|
|
}
|
|
|
|
diff --git a/drivers/gpu/drm/radeon/radeon_cs.c b/drivers/gpu/drm/radeon/radeon_cs.c
|
|
index e9d0850..70ba02e 100644
|
|
--- a/drivers/gpu/drm/radeon/radeon_cs.c
|
|
+++ b/drivers/gpu/drm/radeon/radeon_cs.c
|
|
@@ -194,11 +194,8 @@ static void radeon_cs_parser_fini(struct radeon_cs_parser *parser, int error)
|
|
radeon_bo_list_unreserve(&parser->validated);
|
|
if (parser->relocs != NULL) {
|
|
for (i = 0; i < parser->nrelocs; i++) {
|
|
- if (parser->relocs[i].gobj) {
|
|
- mutex_lock(&parser->rdev->ddev->struct_mutex);
|
|
- drm_gem_object_unreference(parser->relocs[i].gobj);
|
|
- mutex_unlock(&parser->rdev->ddev->struct_mutex);
|
|
- }
|
|
+ if (parser->relocs[i].gobj)
|
|
+ drm_gem_object_unreference_unlocked(parser->relocs[i].gobj);
|
|
}
|
|
}
|
|
kfree(parser->track);
|
|
diff --git a/drivers/gpu/drm/radeon/radeon_cursor.c b/drivers/gpu/drm/radeon/radeon_cursor.c
|
|
index 28772a3..6f4a553 100644
|
|
--- a/drivers/gpu/drm/radeon/radeon_cursor.c
|
|
+++ b/drivers/gpu/drm/radeon/radeon_cursor.c
|
|
@@ -169,17 +169,13 @@ int radeon_crtc_cursor_set(struct drm_crtc *crtc,
|
|
unpin:
|
|
if (radeon_crtc->cursor_bo) {
|
|
radeon_gem_object_unpin(radeon_crtc->cursor_bo);
|
|
- mutex_lock(&crtc->dev->struct_mutex);
|
|
- drm_gem_object_unreference(radeon_crtc->cursor_bo);
|
|
- mutex_unlock(&crtc->dev->struct_mutex);
|
|
+ drm_gem_object_unreference_unlocked(radeon_crtc->cursor_bo);
|
|
}
|
|
|
|
radeon_crtc->cursor_bo = obj;
|
|
return 0;
|
|
fail:
|
|
- mutex_lock(&crtc->dev->struct_mutex);
|
|
- drm_gem_object_unreference(obj);
|
|
- mutex_unlock(&crtc->dev->struct_mutex);
|
|
+ drm_gem_object_unreference_unlocked(obj);
|
|
|
|
return 0;
|
|
}
|
|
diff --git a/drivers/gpu/drm/radeon/radeon_display.c b/drivers/gpu/drm/radeon/radeon_display.c
|
|
index 7e17a36..3db8255 100644
|
|
--- a/drivers/gpu/drm/radeon/radeon_display.c
|
|
+++ b/drivers/gpu/drm/radeon/radeon_display.c
|
|
@@ -679,11 +679,8 @@ static void radeon_user_framebuffer_destroy(struct drm_framebuffer *fb)
|
|
if (fb->fbdev)
|
|
radeonfb_remove(dev, fb);
|
|
|
|
- if (radeon_fb->obj) {
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_unreference(radeon_fb->obj);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
- }
|
|
+ if (radeon_fb->obj)
|
|
+ drm_gem_object_unreference_unlocked(radeon_fb->obj);
|
|
drm_framebuffer_cleanup(fb);
|
|
kfree(radeon_fb);
|
|
}
|
|
diff --git a/drivers/gpu/drm/radeon/radeon_gem.c b/drivers/gpu/drm/radeon/radeon_gem.c
|
|
index db8e9a3..ef92d14 100644
|
|
--- a/drivers/gpu/drm/radeon/radeon_gem.c
|
|
+++ b/drivers/gpu/drm/radeon/radeon_gem.c
|
|
@@ -69,9 +69,7 @@ int radeon_gem_object_create(struct radeon_device *rdev, int size,
|
|
if (r != -ERESTARTSYS)
|
|
DRM_ERROR("Failed to allocate GEM object (%d, %d, %u, %d)\n",
|
|
size, initial_domain, alignment, r);
|
|
- mutex_lock(&rdev->ddev->struct_mutex);
|
|
- drm_gem_object_unreference(gobj);
|
|
- mutex_unlock(&rdev->ddev->struct_mutex);
|
|
+ drm_gem_object_unreference_unlocked(gobj);
|
|
return r;
|
|
}
|
|
gobj->driver_private = robj;
|
|
@@ -202,14 +200,10 @@ int radeon_gem_create_ioctl(struct drm_device *dev, void *data,
|
|
}
|
|
r = drm_gem_handle_create(filp, gobj, &handle);
|
|
if (r) {
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_unreference(gobj);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
+ drm_gem_object_unreference_unlocked(gobj);
|
|
return r;
|
|
}
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_handle_unreference(gobj);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
+ drm_gem_object_handle_unreference_unlocked(gobj);
|
|
args->handle = handle;
|
|
return 0;
|
|
}
|
|
@@ -236,9 +230,7 @@ int radeon_gem_set_domain_ioctl(struct drm_device *dev, void *data,
|
|
|
|
r = radeon_gem_set_domain(gobj, args->read_domains, args->write_domain);
|
|
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_unreference(gobj);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
+ drm_gem_object_unreference_unlocked(gobj);
|
|
return r;
|
|
}
|
|
|
|
@@ -255,9 +247,7 @@ int radeon_gem_mmap_ioctl(struct drm_device *dev, void *data,
|
|
}
|
|
robj = gobj->driver_private;
|
|
args->addr_ptr = radeon_bo_mmap_offset(robj);
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_unreference(gobj);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
+ drm_gem_object_unreference_unlocked(gobj);
|
|
return 0;
|
|
}
|
|
|
|
@@ -288,9 +278,7 @@ int radeon_gem_busy_ioctl(struct drm_device *dev, void *data,
|
|
default:
|
|
break;
|
|
}
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_unreference(gobj);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
+ drm_gem_object_unreference_unlocked(gobj);
|
|
return r;
|
|
}
|
|
|
|
@@ -311,9 +299,7 @@ int radeon_gem_wait_idle_ioctl(struct drm_device *dev, void *data,
|
|
/* callback hw specific functions if any */
|
|
if (robj->rdev->asic->ioctl_wait_idle)
|
|
robj->rdev->asic->ioctl_wait_idle(robj->rdev, robj);
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_unreference(gobj);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
+ drm_gem_object_unreference_unlocked(gobj);
|
|
return r;
|
|
}
|
|
|
|
@@ -331,9 +317,7 @@ int radeon_gem_set_tiling_ioctl(struct drm_device *dev, void *data,
|
|
return -EINVAL;
|
|
robj = gobj->driver_private;
|
|
r = radeon_bo_set_tiling_flags(robj, args->tiling_flags, args->pitch);
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_unreference(gobj);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
+ drm_gem_object_unreference_unlocked(gobj);
|
|
return r;
|
|
}
|
|
|
|
@@ -356,8 +340,6 @@ int radeon_gem_get_tiling_ioctl(struct drm_device *dev, void *data,
|
|
radeon_bo_get_tiling_flags(rbo, &args->tiling_flags, &args->pitch);
|
|
radeon_bo_unreserve(rbo);
|
|
out:
|
|
- mutex_lock(&dev->struct_mutex);
|
|
- drm_gem_object_unreference(gobj);
|
|
- mutex_unlock(&dev->struct_mutex);
|
|
+ drm_gem_object_unreference_unlocked(gobj);
|
|
return r;
|
|
}
|
|
diff --git a/drivers/gpu/drm/ttm/Makefile b/drivers/gpu/drm/ttm/Makefile
|
|
index 1e138f5..4256e20 100644
|
|
--- a/drivers/gpu/drm/ttm/Makefile
|
|
+++ b/drivers/gpu/drm/ttm/Makefile
|
|
@@ -4,6 +4,6 @@
|
|
ccflags-y := -Iinclude/drm
|
|
ttm-y := ttm_agp_backend.o ttm_memory.o ttm_tt.o ttm_bo.o \
|
|
ttm_bo_util.o ttm_bo_vm.o ttm_module.o ttm_global.o \
|
|
- ttm_object.o ttm_lock.o ttm_execbuf_util.o
|
|
+ ttm_object.o ttm_lock.o ttm_execbuf_util.o ttm_page_alloc.o
|
|
|
|
obj-$(CONFIG_DRM_TTM) += ttm.o
|
|
diff --git a/drivers/gpu/drm/ttm/ttm_bo.c b/drivers/gpu/drm/ttm/ttm_bo.c
|
|
index c7320ce..9db02bb 100644
|
|
--- a/drivers/gpu/drm/ttm/ttm_bo.c
|
|
+++ b/drivers/gpu/drm/ttm/ttm_bo.c
|
|
@@ -1425,8 +1425,8 @@ int ttm_bo_global_init(struct ttm_global_reference *ref)
|
|
|
|
atomic_set(&glob->bo_count, 0);
|
|
|
|
- kobject_init(&glob->kobj, &ttm_bo_glob_kobj_type);
|
|
- ret = kobject_add(&glob->kobj, ttm_get_kobj(), "buffer_objects");
|
|
+ ret = kobject_init_and_add(
|
|
+ &glob->kobj, &ttm_bo_glob_kobj_type, ttm_get_kobj(), "buffer_objects");
|
|
if (unlikely(ret != 0))
|
|
kobject_put(&glob->kobj);
|
|
return ret;
|
|
diff --git a/drivers/gpu/drm/ttm/ttm_memory.c b/drivers/gpu/drm/ttm/ttm_memory.c
|
|
index f5245c0..4057a17 100644
|
|
--- a/drivers/gpu/drm/ttm/ttm_memory.c
|
|
+++ b/drivers/gpu/drm/ttm/ttm_memory.c
|
|
@@ -27,6 +27,7 @@
|
|
|
|
#include "ttm/ttm_memory.h"
|
|
#include "ttm/ttm_module.h"
|
|
+#include "ttm/ttm_page_alloc.h"
|
|
#include <linux/spinlock.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/wait.h>
|
|
@@ -260,8 +261,8 @@ static int ttm_mem_init_kernel_zone(struct ttm_mem_global *glob,
|
|
zone->used_mem = 0;
|
|
zone->glob = glob;
|
|
glob->zone_kernel = zone;
|
|
- kobject_init(&zone->kobj, &ttm_mem_zone_kobj_type);
|
|
- ret = kobject_add(&zone->kobj, &glob->kobj, zone->name);
|
|
+ ret = kobject_init_and_add(
|
|
+ &zone->kobj, &ttm_mem_zone_kobj_type, &glob->kobj, zone->name);
|
|
if (unlikely(ret != 0)) {
|
|
kobject_put(&zone->kobj);
|
|
return ret;
|
|
@@ -296,8 +297,8 @@ static int ttm_mem_init_highmem_zone(struct ttm_mem_global *glob,
|
|
zone->used_mem = 0;
|
|
zone->glob = glob;
|
|
glob->zone_highmem = zone;
|
|
- kobject_init(&zone->kobj, &ttm_mem_zone_kobj_type);
|
|
- ret = kobject_add(&zone->kobj, &glob->kobj, zone->name);
|
|
+ ret = kobject_init_and_add(
|
|
+ &zone->kobj, &ttm_mem_zone_kobj_type, &glob->kobj, zone->name);
|
|
if (unlikely(ret != 0)) {
|
|
kobject_put(&zone->kobj);
|
|
return ret;
|
|
@@ -343,8 +344,8 @@ static int ttm_mem_init_dma32_zone(struct ttm_mem_global *glob,
|
|
zone->used_mem = 0;
|
|
zone->glob = glob;
|
|
glob->zone_dma32 = zone;
|
|
- kobject_init(&zone->kobj, &ttm_mem_zone_kobj_type);
|
|
- ret = kobject_add(&zone->kobj, &glob->kobj, zone->name);
|
|
+ ret = kobject_init_and_add(
|
|
+ &zone->kobj, &ttm_mem_zone_kobj_type, &glob->kobj, zone->name);
|
|
if (unlikely(ret != 0)) {
|
|
kobject_put(&zone->kobj);
|
|
return ret;
|
|
@@ -365,10 +366,8 @@ int ttm_mem_global_init(struct ttm_mem_global *glob)
|
|
glob->swap_queue = create_singlethread_workqueue("ttm_swap");
|
|
INIT_WORK(&glob->work, ttm_shrink_work);
|
|
init_waitqueue_head(&glob->queue);
|
|
- kobject_init(&glob->kobj, &ttm_mem_glob_kobj_type);
|
|
- ret = kobject_add(&glob->kobj,
|
|
- ttm_get_kobj(),
|
|
- "memory_accounting");
|
|
+ ret = kobject_init_and_add(
|
|
+ &glob->kobj, &ttm_mem_glob_kobj_type, ttm_get_kobj(), "memory_accounting");
|
|
if (unlikely(ret != 0)) {
|
|
kobject_put(&glob->kobj);
|
|
return ret;
|
|
@@ -394,6 +393,7 @@ int ttm_mem_global_init(struct ttm_mem_global *glob)
|
|
"Zone %7s: Available graphics memory: %llu kiB.\n",
|
|
zone->name, (unsigned long long) zone->max_mem >> 10);
|
|
}
|
|
+ ttm_page_alloc_init(glob, glob->zone_kernel->max_mem/(2*PAGE_SIZE));
|
|
return 0;
|
|
out_no_zone:
|
|
ttm_mem_global_release(glob);
|
|
@@ -406,6 +406,9 @@ void ttm_mem_global_release(struct ttm_mem_global *glob)
|
|
unsigned int i;
|
|
struct ttm_mem_zone *zone;
|
|
|
|
+ /* let the page allocator first stop the shrink work. */
|
|
+ ttm_page_alloc_fini();
|
|
+
|
|
flush_workqueue(glob->swap_queue);
|
|
destroy_workqueue(glob->swap_queue);
|
|
glob->swap_queue = NULL;
|
|
@@ -413,7 +416,7 @@ void ttm_mem_global_release(struct ttm_mem_global *glob)
|
|
zone = glob->zones[i];
|
|
kobject_del(&zone->kobj);
|
|
kobject_put(&zone->kobj);
|
|
- }
|
|
+ }
|
|
kobject_del(&glob->kobj);
|
|
kobject_put(&glob->kobj);
|
|
}
|
|
diff --git a/drivers/gpu/drm/ttm/ttm_page_alloc.c b/drivers/gpu/drm/ttm/ttm_page_alloc.c
|
|
new file mode 100644
|
|
index 0000000..03509f8
|
|
--- /dev/null
|
|
+++ b/drivers/gpu/drm/ttm/ttm_page_alloc.c
|
|
@@ -0,0 +1,855 @@
|
|
+/*
|
|
+ * Copyright (c) Red Hat Inc.
|
|
+
|
|
+ * Permission is hereby granted, free of charge, to any person obtaining a
|
|
+ * copy of this software and associated documentation files (the "Software"),
|
|
+ * to deal in the Software without restriction, including without limitation
|
|
+ * the rights to use, copy, modify, merge, publish, distribute, sub license,
|
|
+ * and/or sell copies of the Software, and to permit persons to whom the
|
|
+ * Software is furnished to do so, subject to the following conditions:
|
|
+ *
|
|
+ * The above copyright notice and this permission notice (including the
|
|
+ * next paragraph) shall be included in all copies or substantial portions
|
|
+ * of the Software.
|
|
+ *
|
|
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
|
|
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
+ * DEALINGS IN THE SOFTWARE.
|
|
+ *
|
|
+ * Authors: Dave Airlie <airlied@redhat.com>
|
|
+ * Jerome Glisse <jglisse@redhat.com>
|
|
+ * Pauli Nieminen <suokkos@gmail.com>
|
|
+ */
|
|
+
|
|
+/* simple list based uncached page pool
|
|
+ * - Pool collects resently freed pages for reuse
|
|
+ * - Use page->lru to keep a free list
|
|
+ * - doesn't track currently in use pages
|
|
+ */
|
|
+#include <linux/list.h>
|
|
+#include <linux/spinlock.h>
|
|
+#include <linux/highmem.h>
|
|
+#include <linux/mm_types.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/mm.h>
|
|
+#include <linux/seq_file.h> /* for seq_printf */
|
|
+#include <linux/slab.h>
|
|
+
|
|
+#include <asm/atomic.h>
|
|
+#include <asm/agp.h>
|
|
+
|
|
+#include "ttm/ttm_bo_driver.h"
|
|
+#include "ttm/ttm_page_alloc.h"
|
|
+
|
|
+
|
|
+#define NUM_PAGES_TO_ALLOC (PAGE_SIZE/sizeof(struct page *))
|
|
+#define SMALL_ALLOCATION 16
|
|
+#define FREE_ALL_PAGES (~0U)
|
|
+/* times are in msecs */
|
|
+#define PAGE_FREE_INTERVAL 1000
|
|
+
|
|
+/**
|
|
+ * struct ttm_page_pool - Pool to reuse recently allocated uc/wc pages.
|
|
+ *
|
|
+ * @lock: Protects the shared pool from concurrnet access. Must be used with
|
|
+ * irqsave/irqrestore variants because pool allocator maybe called from
|
|
+ * delayed work.
|
|
+ * @fill_lock: Prevent concurrent calls to fill.
|
|
+ * @list: Pool of free uc/wc pages for fast reuse.
|
|
+ * @gfp_flags: Flags to pass for alloc_page.
|
|
+ * @npages: Number of pages in pool.
|
|
+ */
|
|
+struct ttm_page_pool {
|
|
+ spinlock_t lock;
|
|
+ bool fill_lock;
|
|
+ struct list_head list;
|
|
+ int gfp_flags;
|
|
+ unsigned npages;
|
|
+ char *name;
|
|
+ unsigned long nfrees;
|
|
+ unsigned long nrefills;
|
|
+};
|
|
+
|
|
+/**
|
|
+ * Limits for the pool. They are handled without locks because only place where
|
|
+ * they may change is in sysfs store. They won't have immediate effect anyway
|
|
+ * so forcing serialiazation to access them is pointless.
|
|
+ */
|
|
+
|
|
+struct ttm_pool_opts {
|
|
+ unsigned alloc_size;
|
|
+ unsigned max_size;
|
|
+ unsigned small;
|
|
+};
|
|
+
|
|
+#define NUM_POOLS 4
|
|
+
|
|
+/**
|
|
+ * struct ttm_pool_manager - Holds memory pools for fst allocation
|
|
+ *
|
|
+ * Manager is read only object for pool code so it doesn't need locking.
|
|
+ *
|
|
+ * @free_interval: minimum number of jiffies between freeing pages from pool.
|
|
+ * @page_alloc_inited: reference counting for pool allocation.
|
|
+ * @work: Work that is used to shrink the pool. Work is only run when there is
|
|
+ * some pages to free.
|
|
+ * @small_allocation: Limit in number of pages what is small allocation.
|
|
+ *
|
|
+ * @pools: All pool objects in use.
|
|
+ **/
|
|
+struct ttm_pool_manager {
|
|
+ struct kobject kobj;
|
|
+ struct shrinker mm_shrink;
|
|
+ atomic_t page_alloc_inited;
|
|
+ struct ttm_pool_opts options;
|
|
+
|
|
+ union {
|
|
+ struct ttm_page_pool pools[NUM_POOLS];
|
|
+ struct {
|
|
+ struct ttm_page_pool wc_pool;
|
|
+ struct ttm_page_pool uc_pool;
|
|
+ struct ttm_page_pool wc_pool_dma32;
|
|
+ struct ttm_page_pool uc_pool_dma32;
|
|
+ } ;
|
|
+ };
|
|
+};
|
|
+
|
|
+static struct attribute ttm_page_pool_max = {
|
|
+ .name = "pool_max_size",
|
|
+ .mode = S_IRUGO | S_IWUSR
|
|
+};
|
|
+static struct attribute ttm_page_pool_small = {
|
|
+ .name = "pool_small_allocation",
|
|
+ .mode = S_IRUGO | S_IWUSR
|
|
+};
|
|
+static struct attribute ttm_page_pool_alloc_size = {
|
|
+ .name = "pool_allocation_size",
|
|
+ .mode = S_IRUGO | S_IWUSR
|
|
+};
|
|
+
|
|
+static struct attribute *ttm_pool_attrs[] = {
|
|
+ &ttm_page_pool_max,
|
|
+ &ttm_page_pool_small,
|
|
+ &ttm_page_pool_alloc_size,
|
|
+ NULL
|
|
+};
|
|
+
|
|
+static void ttm_pool_kobj_release(struct kobject *kobj)
|
|
+{
|
|
+ struct ttm_pool_manager *m =
|
|
+ container_of(kobj, struct ttm_pool_manager, kobj);
|
|
+ (void)m;
|
|
+}
|
|
+
|
|
+static ssize_t ttm_pool_store(struct kobject *kobj,
|
|
+ struct attribute *attr, const char *buffer, size_t size)
|
|
+{
|
|
+ struct ttm_pool_manager *m =
|
|
+ container_of(kobj, struct ttm_pool_manager, kobj);
|
|
+ int chars;
|
|
+ unsigned val;
|
|
+ chars = sscanf(buffer, "%u", &val);
|
|
+ if (chars == 0)
|
|
+ return size;
|
|
+
|
|
+ /* Convert kb to number of pages */
|
|
+ val = val / (PAGE_SIZE >> 10);
|
|
+
|
|
+ if (attr == &ttm_page_pool_max)
|
|
+ m->options.max_size = val;
|
|
+ else if (attr == &ttm_page_pool_small)
|
|
+ m->options.small = val;
|
|
+ else if (attr == &ttm_page_pool_alloc_size) {
|
|
+ if (val > NUM_PAGES_TO_ALLOC*8) {
|
|
+ printk(KERN_ERR "[ttm] Setting allocation size to %lu "
|
|
+ "is not allowed. Recomended size is "
|
|
+ "%lu\n",
|
|
+ NUM_PAGES_TO_ALLOC*(PAGE_SIZE >> 7),
|
|
+ NUM_PAGES_TO_ALLOC*(PAGE_SIZE >> 10));
|
|
+ return size;
|
|
+ } else if (val > NUM_PAGES_TO_ALLOC) {
|
|
+ printk(KERN_WARNING "[ttm] Setting allocation size to "
|
|
+ "larger than %lu is not recomended.\n",
|
|
+ NUM_PAGES_TO_ALLOC*(PAGE_SIZE >> 10));
|
|
+ }
|
|
+ m->options.alloc_size = val;
|
|
+ }
|
|
+
|
|
+ return size;
|
|
+}
|
|
+
|
|
+static ssize_t ttm_pool_show(struct kobject *kobj,
|
|
+ struct attribute *attr, char *buffer)
|
|
+{
|
|
+ struct ttm_pool_manager *m =
|
|
+ container_of(kobj, struct ttm_pool_manager, kobj);
|
|
+ unsigned val = 0;
|
|
+
|
|
+ if (attr == &ttm_page_pool_max)
|
|
+ val = m->options.max_size;
|
|
+ else if (attr == &ttm_page_pool_small)
|
|
+ val = m->options.small;
|
|
+ else if (attr == &ttm_page_pool_alloc_size)
|
|
+ val = m->options.alloc_size;
|
|
+
|
|
+ val = val * (PAGE_SIZE >> 10);
|
|
+
|
|
+ return snprintf(buffer, PAGE_SIZE, "%u\n", val);
|
|
+}
|
|
+
|
|
+static const struct sysfs_ops ttm_pool_sysfs_ops = {
|
|
+ .show = &ttm_pool_show,
|
|
+ .store = &ttm_pool_store,
|
|
+};
|
|
+
|
|
+static struct kobj_type ttm_pool_kobj_type = {
|
|
+ .release = &ttm_pool_kobj_release,
|
|
+ .sysfs_ops = &ttm_pool_sysfs_ops,
|
|
+ .default_attrs = ttm_pool_attrs,
|
|
+};
|
|
+
|
|
+static struct ttm_pool_manager _manager = {
|
|
+ .page_alloc_inited = ATOMIC_INIT(0)
|
|
+};
|
|
+
|
|
+#ifdef CONFIG_X86
|
|
+/* TODO: add this to x86 like _uc, this version here is inefficient */
|
|
+static int set_pages_array_wc(struct page **pages, int addrinarray)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < addrinarray; i++)
|
|
+ set_memory_wc((unsigned long)page_address(pages[i]), 1);
|
|
+ return 0;
|
|
+}
|
|
+#else
|
|
+static int set_pages_array_wb(struct page **pages, int addrinarray)
|
|
+{
|
|
+#ifdef TTM_HAS_AGP
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < addrinarray; i++)
|
|
+ unmap_page_from_agp(pages[i]);
|
|
+#endif
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int set_pages_array_wc(struct page **pages, int addrinarray)
|
|
+{
|
|
+#ifdef TTM_HAS_AGP
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < addrinarray; i++)
|
|
+ map_page_into_agp(pages[i]);
|
|
+#endif
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int set_pages_array_uc(struct page **pages, int addrinarray)
|
|
+{
|
|
+#ifdef TTM_HAS_AGP
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < addrinarray; i++)
|
|
+ map_page_into_agp(pages[i]);
|
|
+#endif
|
|
+ return 0;
|
|
+}
|
|
+#endif
|
|
+
|
|
+/**
|
|
+ * Select the right pool or requested caching state and ttm flags. */
|
|
+static struct ttm_page_pool *ttm_get_pool(int flags,
|
|
+ enum ttm_caching_state cstate)
|
|
+{
|
|
+ int pool_index;
|
|
+
|
|
+ if (cstate == tt_cached)
|
|
+ return NULL;
|
|
+
|
|
+ if (cstate == tt_wc)
|
|
+ pool_index = 0x0;
|
|
+ else
|
|
+ pool_index = 0x1;
|
|
+
|
|
+ if (flags & TTM_PAGE_FLAG_DMA32)
|
|
+ pool_index |= 0x2;
|
|
+
|
|
+ return &_manager.pools[pool_index];
|
|
+}
|
|
+
|
|
+/* set memory back to wb and free the pages. */
|
|
+static void ttm_pages_put(struct page *pages[], unsigned npages)
|
|
+{
|
|
+ unsigned i;
|
|
+ if (set_pages_array_wb(pages, npages))
|
|
+ printk(KERN_ERR "[ttm] Failed to set %d pages to wb!\n",
|
|
+ npages);
|
|
+ for (i = 0; i < npages; ++i)
|
|
+ __free_page(pages[i]);
|
|
+}
|
|
+
|
|
+static void ttm_pool_update_free_locked(struct ttm_page_pool *pool,
|
|
+ unsigned freed_pages)
|
|
+{
|
|
+ pool->npages -= freed_pages;
|
|
+ pool->nfrees += freed_pages;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Free pages from pool.
|
|
+ *
|
|
+ * To prevent hogging the ttm_swap process we only free NUM_PAGES_TO_ALLOC
|
|
+ * number of pages in one go.
|
|
+ *
|
|
+ * @pool: to free the pages from
|
|
+ * @free_all: If set to true will free all pages in pool
|
|
+ **/
|
|
+static int ttm_page_pool_free(struct ttm_page_pool *pool, unsigned nr_free)
|
|
+{
|
|
+ unsigned long irq_flags;
|
|
+ struct page *p;
|
|
+ struct page **pages_to_free;
|
|
+ unsigned freed_pages = 0,
|
|
+ npages_to_free = nr_free;
|
|
+
|
|
+ if (NUM_PAGES_TO_ALLOC < nr_free)
|
|
+ npages_to_free = NUM_PAGES_TO_ALLOC;
|
|
+
|
|
+ pages_to_free = kmalloc(npages_to_free * sizeof(struct page *),
|
|
+ GFP_KERNEL);
|
|
+ if (!pages_to_free) {
|
|
+ printk(KERN_ERR "Failed to allocate memory for pool free operation.\n");
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+restart:
|
|
+ spin_lock_irqsave(&pool->lock, irq_flags);
|
|
+
|
|
+ list_for_each_entry_reverse(p, &pool->list, lru) {
|
|
+ if (freed_pages >= npages_to_free)
|
|
+ break;
|
|
+
|
|
+ pages_to_free[freed_pages++] = p;
|
|
+ /* We can only remove NUM_PAGES_TO_ALLOC at a time. */
|
|
+ if (freed_pages >= NUM_PAGES_TO_ALLOC) {
|
|
+ /* remove range of pages from the pool */
|
|
+ __list_del(p->lru.prev, &pool->list);
|
|
+
|
|
+ ttm_pool_update_free_locked(pool, freed_pages);
|
|
+ /**
|
|
+ * Because changing page caching is costly
|
|
+ * we unlock the pool to prevent stalling.
|
|
+ */
|
|
+ spin_unlock_irqrestore(&pool->lock, irq_flags);
|
|
+
|
|
+ ttm_pages_put(pages_to_free, freed_pages);
|
|
+ if (likely(nr_free != FREE_ALL_PAGES))
|
|
+ nr_free -= freed_pages;
|
|
+
|
|
+ if (NUM_PAGES_TO_ALLOC >= nr_free)
|
|
+ npages_to_free = nr_free;
|
|
+ else
|
|
+ npages_to_free = NUM_PAGES_TO_ALLOC;
|
|
+
|
|
+ freed_pages = 0;
|
|
+
|
|
+ /* free all so restart the processing */
|
|
+ if (nr_free)
|
|
+ goto restart;
|
|
+
|
|
+ /* Not allowed to fall tough or break because
|
|
+ * following context is inside spinlock while we are
|
|
+ * outside here.
|
|
+ */
|
|
+ goto out;
|
|
+
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* remove range of pages from the pool */
|
|
+ if (freed_pages) {
|
|
+ __list_del(&p->lru, &pool->list);
|
|
+
|
|
+ ttm_pool_update_free_locked(pool, freed_pages);
|
|
+ nr_free -= freed_pages;
|
|
+ }
|
|
+
|
|
+ spin_unlock_irqrestore(&pool->lock, irq_flags);
|
|
+
|
|
+ if (freed_pages)
|
|
+ ttm_pages_put(pages_to_free, freed_pages);
|
|
+out:
|
|
+ kfree(pages_to_free);
|
|
+ return nr_free;
|
|
+}
|
|
+
|
|
+/* Get good estimation how many pages are free in pools */
|
|
+static int ttm_pool_get_num_unused_pages(void)
|
|
+{
|
|
+ unsigned i;
|
|
+ int total = 0;
|
|
+ for (i = 0; i < NUM_POOLS; ++i)
|
|
+ total += _manager.pools[i].npages;
|
|
+
|
|
+ return total;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Calback for mm to request pool to reduce number of page held.
|
|
+ */
|
|
+static int ttm_pool_mm_shrink(int shrink_pages, gfp_t gfp_mask)
|
|
+{
|
|
+ static atomic_t start_pool = ATOMIC_INIT(0);
|
|
+ unsigned i;
|
|
+ unsigned pool_offset = atomic_add_return(1, &start_pool);
|
|
+ struct ttm_page_pool *pool;
|
|
+
|
|
+ pool_offset = pool_offset % NUM_POOLS;
|
|
+ /* select start pool in round robin fashion */
|
|
+ for (i = 0; i < NUM_POOLS; ++i) {
|
|
+ unsigned nr_free = shrink_pages;
|
|
+ if (shrink_pages == 0)
|
|
+ break;
|
|
+ pool = &_manager.pools[(i + pool_offset)%NUM_POOLS];
|
|
+ shrink_pages = ttm_page_pool_free(pool, nr_free);
|
|
+ }
|
|
+ /* return estimated number of unused pages in pool */
|
|
+ return ttm_pool_get_num_unused_pages();
|
|
+}
|
|
+
|
|
+static void ttm_pool_mm_shrink_init(struct ttm_pool_manager *manager)
|
|
+{
|
|
+ manager->mm_shrink.shrink = &ttm_pool_mm_shrink;
|
|
+ manager->mm_shrink.seeks = 1;
|
|
+ register_shrinker(&manager->mm_shrink);
|
|
+}
|
|
+
|
|
+static void ttm_pool_mm_shrink_fini(struct ttm_pool_manager *manager)
|
|
+{
|
|
+ unregister_shrinker(&manager->mm_shrink);
|
|
+}
|
|
+
|
|
+static int ttm_set_pages_caching(struct page **pages,
|
|
+ enum ttm_caching_state cstate, unsigned cpages)
|
|
+{
|
|
+ int r = 0;
|
|
+ /* Set page caching */
|
|
+ switch (cstate) {
|
|
+ case tt_uncached:
|
|
+ r = set_pages_array_uc(pages, cpages);
|
|
+ if (r)
|
|
+ printk(KERN_ERR "[ttm] Failed to set %d pages to uc!\n",
|
|
+ cpages);
|
|
+ break;
|
|
+ case tt_wc:
|
|
+ r = set_pages_array_wc(pages, cpages);
|
|
+ if (r)
|
|
+ printk(KERN_ERR "[ttm] Failed to set %d pages to wc!\n",
|
|
+ cpages);
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+ return r;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Free pages the pages that failed to change the caching state. If there is
|
|
+ * any pages that have changed their caching state already put them to the
|
|
+ * pool.
|
|
+ */
|
|
+static void ttm_handle_caching_state_failure(struct list_head *pages,
|
|
+ int ttm_flags, enum ttm_caching_state cstate,
|
|
+ struct page **failed_pages, unsigned cpages)
|
|
+{
|
|
+ unsigned i;
|
|
+ /* Failed pages has to be reed */
|
|
+ for (i = 0; i < cpages; ++i) {
|
|
+ list_del(&failed_pages[i]->lru);
|
|
+ __free_page(failed_pages[i]);
|
|
+ }
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Allocate new pages with correct caching.
|
|
+ *
|
|
+ * This function is reentrant if caller updates count depending on number of
|
|
+ * pages returned in pages array.
|
|
+ */
|
|
+static int ttm_alloc_new_pages(struct list_head *pages, int gfp_flags,
|
|
+ int ttm_flags, enum ttm_caching_state cstate, unsigned count)
|
|
+{
|
|
+ struct page **caching_array;
|
|
+ struct page *p;
|
|
+ int r = 0;
|
|
+ unsigned i, cpages;
|
|
+ unsigned max_cpages = min(count,
|
|
+ (unsigned)(PAGE_SIZE/sizeof(struct page *)));
|
|
+
|
|
+ /* allocate array for page caching change */
|
|
+ caching_array = kmalloc(max_cpages*sizeof(struct page *), GFP_KERNEL);
|
|
+
|
|
+ if (!caching_array) {
|
|
+ printk(KERN_ERR "[ttm] unable to allocate table for new pages.");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ for (i = 0, cpages = 0; i < count; ++i) {
|
|
+ p = alloc_page(gfp_flags);
|
|
+
|
|
+ if (!p) {
|
|
+ printk(KERN_ERR "[ttm] unable to get page %u\n", i);
|
|
+
|
|
+ /* store already allocated pages in the pool after
|
|
+ * setting the caching state */
|
|
+ if (cpages) {
|
|
+ r = ttm_set_pages_caching(caching_array, cstate, cpages);
|
|
+ if (r)
|
|
+ ttm_handle_caching_state_failure(pages,
|
|
+ ttm_flags, cstate,
|
|
+ caching_array, cpages);
|
|
+ }
|
|
+ r = -ENOMEM;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+#ifdef CONFIG_HIGHMEM
|
|
+ /* gfp flags of highmem page should never be dma32 so we
|
|
+ * we should be fine in such case
|
|
+ */
|
|
+ if (!PageHighMem(p))
|
|
+#endif
|
|
+ {
|
|
+ caching_array[cpages++] = p;
|
|
+ if (cpages == max_cpages) {
|
|
+
|
|
+ r = ttm_set_pages_caching(caching_array,
|
|
+ cstate, cpages);
|
|
+ if (r) {
|
|
+ ttm_handle_caching_state_failure(pages,
|
|
+ ttm_flags, cstate,
|
|
+ caching_array, cpages);
|
|
+ goto out;
|
|
+ }
|
|
+ cpages = 0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ list_add(&p->lru, pages);
|
|
+ }
|
|
+
|
|
+ if (cpages) {
|
|
+ r = ttm_set_pages_caching(caching_array, cstate, cpages);
|
|
+ if (r)
|
|
+ ttm_handle_caching_state_failure(pages,
|
|
+ ttm_flags, cstate,
|
|
+ caching_array, cpages);
|
|
+ }
|
|
+out:
|
|
+ kfree(caching_array);
|
|
+
|
|
+ return r;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Fill the given pool if there isn't enough pages and requested number of
|
|
+ * pages is small.
|
|
+ */
|
|
+static void ttm_page_pool_fill_locked(struct ttm_page_pool *pool,
|
|
+ int ttm_flags, enum ttm_caching_state cstate, unsigned count,
|
|
+ unsigned long *irq_flags)
|
|
+{
|
|
+ struct page *p;
|
|
+ int r;
|
|
+ unsigned cpages = 0;
|
|
+ /**
|
|
+ * Only allow one pool fill operation at a time.
|
|
+ * If pool doesn't have enough pages for the allocation new pages are
|
|
+ * allocated from outside of pool.
|
|
+ */
|
|
+ if (pool->fill_lock)
|
|
+ return;
|
|
+
|
|
+ pool->fill_lock = true;
|
|
+
|
|
+ /* If allocation request is small and there is not enough
|
|
+ * pages in pool we fill the pool first */
|
|
+ if (count < _manager.options.small
|
|
+ && count > pool->npages) {
|
|
+ struct list_head new_pages;
|
|
+ unsigned alloc_size = _manager.options.alloc_size;
|
|
+
|
|
+ /**
|
|
+ * Can't change page caching if in irqsave context. We have to
|
|
+ * drop the pool->lock.
|
|
+ */
|
|
+ spin_unlock_irqrestore(&pool->lock, *irq_flags);
|
|
+
|
|
+ INIT_LIST_HEAD(&new_pages);
|
|
+ r = ttm_alloc_new_pages(&new_pages, pool->gfp_flags, ttm_flags,
|
|
+ cstate, alloc_size);
|
|
+ spin_lock_irqsave(&pool->lock, *irq_flags);
|
|
+
|
|
+ if (!r) {
|
|
+ list_splice(&new_pages, &pool->list);
|
|
+ ++pool->nrefills;
|
|
+ pool->npages += alloc_size;
|
|
+ } else {
|
|
+ printk(KERN_ERR "[ttm] Failed to fill pool (%p).", pool);
|
|
+ /* If we have any pages left put them to the pool. */
|
|
+ list_for_each_entry(p, &pool->list, lru) {
|
|
+ ++cpages;
|
|
+ }
|
|
+ list_splice(&new_pages, &pool->list);
|
|
+ pool->npages += cpages;
|
|
+ }
|
|
+
|
|
+ }
|
|
+ pool->fill_lock = false;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Cut count nubmer of pages from the pool and put them to return list
|
|
+ *
|
|
+ * @return count of pages still to allocate to fill the request.
|
|
+ */
|
|
+static unsigned ttm_page_pool_get_pages(struct ttm_page_pool *pool,
|
|
+ struct list_head *pages, int ttm_flags,
|
|
+ enum ttm_caching_state cstate, unsigned count)
|
|
+{
|
|
+ unsigned long irq_flags;
|
|
+ struct list_head *p;
|
|
+ unsigned i;
|
|
+
|
|
+ spin_lock_irqsave(&pool->lock, irq_flags);
|
|
+ ttm_page_pool_fill_locked(pool, ttm_flags, cstate, count, &irq_flags);
|
|
+
|
|
+ if (count >= pool->npages) {
|
|
+ /* take all pages from the pool */
|
|
+ list_splice_init(&pool->list, pages);
|
|
+ count -= pool->npages;
|
|
+ pool->npages = 0;
|
|
+ goto out;
|
|
+ }
|
|
+ /* find the last pages to include for requested number of pages. Split
|
|
+ * pool to begin and halves to reduce search space. */
|
|
+ if (count <= pool->npages/2) {
|
|
+ i = 0;
|
|
+ list_for_each(p, &pool->list) {
|
|
+ if (++i == count)
|
|
+ break;
|
|
+ }
|
|
+ } else {
|
|
+ i = pool->npages + 1;
|
|
+ list_for_each_prev(p, &pool->list) {
|
|
+ if (--i == count)
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ /* Cut count number of pages from pool */
|
|
+ list_cut_position(pages, &pool->list, p);
|
|
+ pool->npages -= count;
|
|
+ count = 0;
|
|
+out:
|
|
+ spin_unlock_irqrestore(&pool->lock, irq_flags);
|
|
+ return count;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * On success pages list will hold count number of correctly
|
|
+ * cached pages.
|
|
+ */
|
|
+int ttm_get_pages(struct list_head *pages, int flags,
|
|
+ enum ttm_caching_state cstate, unsigned count)
|
|
+{
|
|
+ struct ttm_page_pool *pool = ttm_get_pool(flags, cstate);
|
|
+ struct page *p = NULL;
|
|
+ int gfp_flags = 0;
|
|
+ int r;
|
|
+
|
|
+ /* set zero flag for page allocation if required */
|
|
+ if (flags & TTM_PAGE_FLAG_ZERO_ALLOC)
|
|
+ gfp_flags |= __GFP_ZERO;
|
|
+
|
|
+ /* No pool for cached pages */
|
|
+ if (pool == NULL) {
|
|
+ if (flags & TTM_PAGE_FLAG_DMA32)
|
|
+ gfp_flags |= GFP_DMA32;
|
|
+ else
|
|
+ gfp_flags |= __GFP_HIGHMEM;
|
|
+
|
|
+ for (r = 0; r < count; ++r) {
|
|
+ p = alloc_page(gfp_flags);
|
|
+ if (!p) {
|
|
+
|
|
+ printk(KERN_ERR "[ttm] unable to allocate page.");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ list_add(&p->lru, pages);
|
|
+ }
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+
|
|
+ /* combine zero flag to pool flags */
|
|
+ gfp_flags |= pool->gfp_flags;
|
|
+
|
|
+ /* First we take pages from the pool */
|
|
+ count = ttm_page_pool_get_pages(pool, pages, flags, cstate, count);
|
|
+
|
|
+ /* clear the pages coming from the pool if requested */
|
|
+ if (flags & TTM_PAGE_FLAG_ZERO_ALLOC) {
|
|
+ list_for_each_entry(p, pages, lru) {
|
|
+ clear_page(page_address(p));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* If pool didn't have enough pages allocate new one. */
|
|
+ if (count > 0) {
|
|
+ /* ttm_alloc_new_pages doesn't reference pool so we can run
|
|
+ * multiple requests in parallel.
|
|
+ **/
|
|
+ r = ttm_alloc_new_pages(pages, gfp_flags, flags, cstate, count);
|
|
+ if (r) {
|
|
+ /* If there is any pages in the list put them back to
|
|
+ * the pool. */
|
|
+ printk(KERN_ERR "[ttm] Failed to allocate extra pages "
|
|
+ "for large request.");
|
|
+ ttm_put_pages(pages, 0, flags, cstate);
|
|
+ return r;
|
|
+ }
|
|
+ }
|
|
+
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Put all pages in pages list to correct pool to wait for reuse */
|
|
+void ttm_put_pages(struct list_head *pages, unsigned page_count, int flags,
|
|
+ enum ttm_caching_state cstate)
|
|
+{
|
|
+ unsigned long irq_flags;
|
|
+ struct ttm_page_pool *pool = ttm_get_pool(flags, cstate);
|
|
+ struct page *p, *tmp;
|
|
+
|
|
+ if (pool == NULL) {
|
|
+ /* No pool for this memory type so free the pages */
|
|
+
|
|
+ list_for_each_entry_safe(p, tmp, pages, lru) {
|
|
+ __free_page(p);
|
|
+ }
|
|
+ /* Make the pages list empty */
|
|
+ INIT_LIST_HEAD(pages);
|
|
+ return;
|
|
+ }
|
|
+ if (page_count == 0) {
|
|
+ list_for_each_entry_safe(p, tmp, pages, lru) {
|
|
+ ++page_count;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ spin_lock_irqsave(&pool->lock, irq_flags);
|
|
+ list_splice_init(pages, &pool->list);
|
|
+ pool->npages += page_count;
|
|
+ /* Check that we don't go over the pool limit */
|
|
+ page_count = 0;
|
|
+ if (pool->npages > _manager.options.max_size) {
|
|
+ page_count = pool->npages - _manager.options.max_size;
|
|
+ /* free at least NUM_PAGES_TO_ALLOC number of pages
|
|
+ * to reduce calls to set_memory_wb */
|
|
+ if (page_count < NUM_PAGES_TO_ALLOC)
|
|
+ page_count = NUM_PAGES_TO_ALLOC;
|
|
+ }
|
|
+ spin_unlock_irqrestore(&pool->lock, irq_flags);
|
|
+ if (page_count)
|
|
+ ttm_page_pool_free(pool, page_count);
|
|
+}
|
|
+
|
|
+static void ttm_page_pool_init_locked(struct ttm_page_pool *pool, int flags,
|
|
+ char *name)
|
|
+{
|
|
+ spin_lock_init(&pool->lock);
|
|
+ pool->fill_lock = false;
|
|
+ INIT_LIST_HEAD(&pool->list);
|
|
+ pool->npages = pool->nfrees = 0;
|
|
+ pool->gfp_flags = flags;
|
|
+ pool->name = name;
|
|
+}
|
|
+
|
|
+int ttm_page_alloc_init(struct ttm_mem_global *glob, unsigned max_pages)
|
|
+{
|
|
+ int ret;
|
|
+ if (atomic_add_return(1, &_manager.page_alloc_inited) > 1)
|
|
+ return 0;
|
|
+
|
|
+ printk(KERN_INFO "[ttm] Initializing pool allocator.\n");
|
|
+
|
|
+ ttm_page_pool_init_locked(&_manager.wc_pool, GFP_HIGHUSER, "wc");
|
|
+
|
|
+ ttm_page_pool_init_locked(&_manager.uc_pool, GFP_HIGHUSER, "uc");
|
|
+
|
|
+ ttm_page_pool_init_locked(&_manager.wc_pool_dma32, GFP_USER | GFP_DMA32,
|
|
+ "wc dma");
|
|
+
|
|
+ ttm_page_pool_init_locked(&_manager.uc_pool_dma32, GFP_USER | GFP_DMA32,
|
|
+ "uc dma");
|
|
+
|
|
+ _manager.options.max_size = max_pages;
|
|
+ _manager.options.small = SMALL_ALLOCATION;
|
|
+ _manager.options.alloc_size = NUM_PAGES_TO_ALLOC;
|
|
+
|
|
+ kobject_init(&_manager.kobj, &ttm_pool_kobj_type);
|
|
+ ret = kobject_add(&_manager.kobj, &glob->kobj, "pool");
|
|
+ if (unlikely(ret != 0)) {
|
|
+ kobject_put(&_manager.kobj);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ttm_pool_mm_shrink_init(&_manager);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void ttm_page_alloc_fini()
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ if (atomic_sub_return(1, &_manager.page_alloc_inited) > 0)
|
|
+ return;
|
|
+
|
|
+ printk(KERN_INFO "[ttm] Finilizing pool allocator.\n");
|
|
+ ttm_pool_mm_shrink_fini(&_manager);
|
|
+
|
|
+ for (i = 0; i < NUM_POOLS; ++i)
|
|
+ ttm_page_pool_free(&_manager.pools[i], FREE_ALL_PAGES);
|
|
+
|
|
+ kobject_put(&_manager.kobj);
|
|
+}
|
|
+
|
|
+int ttm_page_alloc_debugfs(struct seq_file *m, void *data)
|
|
+{
|
|
+ struct ttm_page_pool *p;
|
|
+ unsigned i;
|
|
+ char *h[] = {"pool", "refills", "pages freed", "size"};
|
|
+ if (atomic_read(&_manager.page_alloc_inited) == 0) {
|
|
+ seq_printf(m, "No pool allocator running.\n");
|
|
+ return 0;
|
|
+ }
|
|
+ seq_printf(m, "%6s %12s %13s %8s\n",
|
|
+ h[0], h[1], h[2], h[3]);
|
|
+ for (i = 0; i < NUM_POOLS; ++i) {
|
|
+ p = &_manager.pools[i];
|
|
+
|
|
+ seq_printf(m, "%6s %12ld %13ld %8d\n",
|
|
+ p->name, p->nrefills,
|
|
+ p->nfrees, p->npages);
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL(ttm_page_alloc_debugfs);
|
|
diff --git a/drivers/gpu/drm/ttm/ttm_tt.c b/drivers/gpu/drm/ttm/ttm_tt.c
|
|
index a759170..a3269ef 100644
|
|
--- a/drivers/gpu/drm/ttm/ttm_tt.c
|
|
+++ b/drivers/gpu/drm/ttm/ttm_tt.c
|
|
@@ -28,65 +28,34 @@
|
|
* Authors: Thomas Hellstrom <thellstrom-at-vmware-dot-com>
|
|
*/
|
|
|
|
-#include <linux/vmalloc.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/highmem.h>
|
|
#include <linux/pagemap.h>
|
|
#include <linux/file.h>
|
|
#include <linux/swap.h>
|
|
#include "drm_cache.h"
|
|
+#include "drm_mem_util.h"
|
|
#include "ttm/ttm_module.h"
|
|
#include "ttm/ttm_bo_driver.h"
|
|
#include "ttm/ttm_placement.h"
|
|
+#include "ttm/ttm_page_alloc.h"
|
|
|
|
static int ttm_tt_swapin(struct ttm_tt *ttm);
|
|
|
|
/**
|
|
* Allocates storage for pointers to the pages that back the ttm.
|
|
- *
|
|
- * Uses kmalloc if possible. Otherwise falls back to vmalloc.
|
|
*/
|
|
static void ttm_tt_alloc_page_directory(struct ttm_tt *ttm)
|
|
{
|
|
- unsigned long size = ttm->num_pages * sizeof(*ttm->pages);
|
|
- ttm->pages = NULL;
|
|
-
|
|
- if (size <= PAGE_SIZE)
|
|
- ttm->pages = kzalloc(size, GFP_KERNEL);
|
|
-
|
|
- if (!ttm->pages) {
|
|
- ttm->pages = vmalloc_user(size);
|
|
- if (ttm->pages)
|
|
- ttm->page_flags |= TTM_PAGE_FLAG_VMALLOC;
|
|
- }
|
|
+ ttm->pages = drm_calloc_large(ttm->num_pages, sizeof(*ttm->pages));
|
|
}
|
|
|
|
static void ttm_tt_free_page_directory(struct ttm_tt *ttm)
|
|
{
|
|
- if (ttm->page_flags & TTM_PAGE_FLAG_VMALLOC) {
|
|
- vfree(ttm->pages);
|
|
- ttm->page_flags &= ~TTM_PAGE_FLAG_VMALLOC;
|
|
- } else {
|
|
- kfree(ttm->pages);
|
|
- }
|
|
+ drm_free_large(ttm->pages);
|
|
ttm->pages = NULL;
|
|
}
|
|
|
|
-static struct page *ttm_tt_alloc_page(unsigned page_flags)
|
|
-{
|
|
- gfp_t gfp_flags = GFP_USER;
|
|
-
|
|
- if (page_flags & TTM_PAGE_FLAG_ZERO_ALLOC)
|
|
- gfp_flags |= __GFP_ZERO;
|
|
-
|
|
- if (page_flags & TTM_PAGE_FLAG_DMA32)
|
|
- gfp_flags |= __GFP_DMA32;
|
|
- else
|
|
- gfp_flags |= __GFP_HIGHMEM;
|
|
-
|
|
- return alloc_page(gfp_flags);
|
|
-}
|
|
-
|
|
static void ttm_tt_free_user_pages(struct ttm_tt *ttm)
|
|
{
|
|
int write;
|
|
@@ -127,15 +96,21 @@ static void ttm_tt_free_user_pages(struct ttm_tt *ttm)
|
|
static struct page *__ttm_tt_get_page(struct ttm_tt *ttm, int index)
|
|
{
|
|
struct page *p;
|
|
+ struct list_head h;
|
|
struct ttm_mem_global *mem_glob = ttm->glob->mem_glob;
|
|
int ret;
|
|
|
|
while (NULL == (p = ttm->pages[index])) {
|
|
- p = ttm_tt_alloc_page(ttm->page_flags);
|
|
|
|
- if (!p)
|
|
+ INIT_LIST_HEAD(&h);
|
|
+
|
|
+ ret = ttm_get_pages(&h, ttm->page_flags, ttm->caching_state, 1);
|
|
+
|
|
+ if (ret != 0)
|
|
return NULL;
|
|
|
|
+ p = list_first_entry(&h, struct page, lru);
|
|
+
|
|
ret = ttm_mem_global_alloc_page(mem_glob, p, false, false);
|
|
if (unlikely(ret != 0))
|
|
goto out_err;
|
|
@@ -244,10 +219,10 @@ static int ttm_tt_set_caching(struct ttm_tt *ttm,
|
|
if (ttm->caching_state == c_state)
|
|
return 0;
|
|
|
|
- if (c_state != tt_cached) {
|
|
- ret = ttm_tt_populate(ttm);
|
|
- if (unlikely(ret != 0))
|
|
- return ret;
|
|
+ if (ttm->state == tt_unpopulated) {
|
|
+ /* Change caching but don't populate */
|
|
+ ttm->caching_state = c_state;
|
|
+ return 0;
|
|
}
|
|
|
|
if (ttm->caching_state == tt_cached)
|
|
@@ -298,13 +273,17 @@ EXPORT_SYMBOL(ttm_tt_set_placement_caching);
|
|
static void ttm_tt_free_alloced_pages(struct ttm_tt *ttm)
|
|
{
|
|
int i;
|
|
+ unsigned count = 0;
|
|
+ struct list_head h;
|
|
struct page *cur_page;
|
|
struct ttm_backend *be = ttm->be;
|
|
|
|
+ INIT_LIST_HEAD(&h);
|
|
+
|
|
if (be)
|
|
be->func->clear(be);
|
|
- (void)ttm_tt_set_caching(ttm, tt_cached);
|
|
for (i = 0; i < ttm->num_pages; ++i) {
|
|
+
|
|
cur_page = ttm->pages[i];
|
|
ttm->pages[i] = NULL;
|
|
if (cur_page) {
|
|
@@ -314,9 +293,11 @@ static void ttm_tt_free_alloced_pages(struct ttm_tt *ttm)
|
|
"Leaking pages.\n");
|
|
ttm_mem_global_free_page(ttm->glob->mem_glob,
|
|
cur_page);
|
|
- __free_page(cur_page);
|
|
+ list_add(&cur_page->lru, &h);
|
|
+ count++;
|
|
}
|
|
}
|
|
+ ttm_put_pages(&h, count, ttm->page_flags, ttm->caching_state);
|
|
ttm->state = tt_unpopulated;
|
|
ttm->first_himem_page = ttm->num_pages;
|
|
ttm->last_lomem_page = -1;
|
|
diff --git a/include/drm/drmP.h b/include/drm/drmP.h
|
|
index ffac157..de2f82e 100644
|
|
--- a/include/drm/drmP.h
|
|
+++ b/include/drm/drmP.h
|
|
@@ -801,6 +801,7 @@ struct drm_driver {
|
|
*/
|
|
int (*gem_init_object) (struct drm_gem_object *obj);
|
|
void (*gem_free_object) (struct drm_gem_object *obj);
|
|
+ void (*gem_free_object_unlocked) (struct drm_gem_object *obj);
|
|
|
|
/* vga arb irq handler */
|
|
void (*vgaarb_irq)(struct drm_device *dev, bool state);
|
|
@@ -1427,6 +1428,7 @@ extern void drm_sysfs_connector_remove(struct drm_connector *connector);
|
|
int drm_gem_init(struct drm_device *dev);
|
|
void drm_gem_destroy(struct drm_device *dev);
|
|
void drm_gem_object_free(struct kref *kref);
|
|
+void drm_gem_object_free_unlocked(struct kref *kref);
|
|
struct drm_gem_object *drm_gem_object_alloc(struct drm_device *dev,
|
|
size_t size);
|
|
void drm_gem_object_handle_free(struct kref *kref);
|
|
@@ -1443,10 +1445,15 @@ drm_gem_object_reference(struct drm_gem_object *obj)
|
|
static inline void
|
|
drm_gem_object_unreference(struct drm_gem_object *obj)
|
|
{
|
|
- if (obj == NULL)
|
|
- return;
|
|
+ if (obj != NULL)
|
|
+ kref_put(&obj->refcount, drm_gem_object_free);
|
|
+}
|
|
|
|
- kref_put(&obj->refcount, drm_gem_object_free);
|
|
+static inline void
|
|
+drm_gem_object_unreference_unlocked(struct drm_gem_object *obj)
|
|
+{
|
|
+ if (obj != NULL)
|
|
+ kref_put(&obj->refcount, drm_gem_object_free_unlocked);
|
|
}
|
|
|
|
int drm_gem_handle_create(struct drm_file *file_priv,
|
|
@@ -1475,6 +1482,21 @@ drm_gem_object_handle_unreference(struct drm_gem_object *obj)
|
|
drm_gem_object_unreference(obj);
|
|
}
|
|
|
|
+static inline void
|
|
+drm_gem_object_handle_unreference_unlocked(struct drm_gem_object *obj)
|
|
+{
|
|
+ if (obj == NULL)
|
|
+ return;
|
|
+
|
|
+ /*
|
|
+ * Must bump handle count first as this may be the last
|
|
+ * ref, in which case the object would disappear before we
|
|
+ * checked for a name
|
|
+ */
|
|
+ kref_put(&obj->handlecount, drm_gem_object_handle_free);
|
|
+ drm_gem_object_unreference_unlocked(obj);
|
|
+}
|
|
+
|
|
struct drm_gem_object *drm_gem_object_lookup(struct drm_device *dev,
|
|
struct drm_file *filp,
|
|
u32 handle);
|
|
@@ -1523,39 +1545,7 @@ static __inline__ void drm_core_dropmap(struct drm_local_map *map)
|
|
{
|
|
}
|
|
|
|
-
|
|
-static __inline__ void *drm_calloc_large(size_t nmemb, size_t size)
|
|
-{
|
|
- if (size != 0 && nmemb > ULONG_MAX / size)
|
|
- return NULL;
|
|
-
|
|
- if (size * nmemb <= PAGE_SIZE)
|
|
- return kcalloc(nmemb, size, GFP_KERNEL);
|
|
-
|
|
- return __vmalloc(size * nmemb,
|
|
- GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO, PAGE_KERNEL);
|
|
-}
|
|
-
|
|
-/* Modeled after cairo's malloc_ab, it's like calloc but without the zeroing. */
|
|
-static __inline__ void *drm_malloc_ab(size_t nmemb, size_t size)
|
|
-{
|
|
- if (size != 0 && nmemb > ULONG_MAX / size)
|
|
- return NULL;
|
|
-
|
|
- if (size * nmemb <= PAGE_SIZE)
|
|
- return kmalloc(nmemb * size, GFP_KERNEL);
|
|
-
|
|
- return __vmalloc(size * nmemb,
|
|
- GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL);
|
|
-}
|
|
-
|
|
-static __inline void drm_free_large(void *ptr)
|
|
-{
|
|
- if (!is_vmalloc_addr(ptr))
|
|
- return kfree(ptr);
|
|
-
|
|
- vfree(ptr);
|
|
-}
|
|
+#include "drm_mem_util.h"
|
|
/*@}*/
|
|
|
|
#endif /* __KERNEL__ */
|
|
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
|
|
index fdf43ab..f3cc7a6 100644
|
|
--- a/include/drm/drm_crtc.h
|
|
+++ b/include/drm/drm_crtc.h
|
|
@@ -666,8 +666,6 @@ extern void drm_fb_release(struct drm_file *file_priv);
|
|
extern int drm_mode_group_init_legacy_group(struct drm_device *dev, struct drm_mode_group *group);
|
|
extern struct edid *drm_get_edid(struct drm_connector *connector,
|
|
struct i2c_adapter *adapter);
|
|
-extern int drm_do_probe_ddc_edid(struct i2c_adapter *adapter,
|
|
- unsigned char *buf, int len);
|
|
extern int drm_add_edid_modes(struct drm_connector *connector, struct edid *edid);
|
|
extern void drm_mode_probed_add(struct drm_connector *connector, struct drm_display_mode *mode);
|
|
extern void drm_mode_remove(struct drm_connector *connector, struct drm_display_mode *mode);
|
|
@@ -799,6 +797,10 @@ extern struct drm_display_mode *drm_cvt_mode(struct drm_device *dev,
|
|
extern struct drm_display_mode *drm_gtf_mode(struct drm_device *dev,
|
|
int hdisplay, int vdisplay, int vrefresh,
|
|
bool interlaced, int margins);
|
|
+extern struct drm_display_mode *drm_gtf_mode_complex(struct drm_device *dev,
|
|
+ int hdisplay, int vdisplay, int vrefresh,
|
|
+ bool interlaced, int margins, int GTF_M,
|
|
+ int GTF_2C, int GTF_K, int GTF_2J);
|
|
extern int drm_add_modes_noedid(struct drm_connector *connector,
|
|
int hdisplay, int vdisplay);
|
|
#endif /* __DRM_CRTC_H__ */
|
|
diff --git a/include/drm/drm_mem_util.h b/include/drm/drm_mem_util.h
|
|
new file mode 100644
|
|
index 0000000..6bd325f
|
|
--- /dev/null
|
|
+++ b/include/drm/drm_mem_util.h
|
|
@@ -0,0 +1,65 @@
|
|
+/*
|
|
+ * Copyright © 2008 Intel Corporation
|
|
+ *
|
|
+ * Permission is hereby granted, free of charge, to any person obtaining a
|
|
+ * copy of this software and associated documentation files (the "Software"),
|
|
+ * to deal in the Software without restriction, including without limitation
|
|
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
+ * and/or sell copies of the Software, and to permit persons to whom the
|
|
+ * Software is furnished to do so, subject to the following conditions:
|
|
+ *
|
|
+ * The above copyright notice and this permission notice (including the next
|
|
+ * paragraph) shall be included in all copies or substantial portions of the
|
|
+ * Software.
|
|
+ *
|
|
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
+ * IN THE SOFTWARE.
|
|
+ *
|
|
+ * Authors:
|
|
+ * Jesse Barnes <jbarnes@virtuousgeek.org>
|
|
+ *
|
|
+ */
|
|
+#ifndef _DRM_MEM_UTIL_H_
|
|
+#define _DRM_MEM_UTIL_H_
|
|
+
|
|
+#include <linux/vmalloc.h>
|
|
+
|
|
+static __inline__ void *drm_calloc_large(size_t nmemb, size_t size)
|
|
+{
|
|
+ if (size != 0 && nmemb > ULONG_MAX / size)
|
|
+ return NULL;
|
|
+
|
|
+ if (size * nmemb <= PAGE_SIZE)
|
|
+ return kcalloc(nmemb, size, GFP_KERNEL);
|
|
+
|
|
+ return __vmalloc(size * nmemb,
|
|
+ GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO, PAGE_KERNEL);
|
|
+}
|
|
+
|
|
+/* Modeled after cairo's malloc_ab, it's like calloc but without the zeroing. */
|
|
+static __inline__ void *drm_malloc_ab(size_t nmemb, size_t size)
|
|
+{
|
|
+ if (size != 0 && nmemb > ULONG_MAX / size)
|
|
+ return NULL;
|
|
+
|
|
+ if (size * nmemb <= PAGE_SIZE)
|
|
+ return kmalloc(nmemb * size, GFP_KERNEL);
|
|
+
|
|
+ return __vmalloc(size * nmemb,
|
|
+ GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL);
|
|
+}
|
|
+
|
|
+static __inline void drm_free_large(void *ptr)
|
|
+{
|
|
+ if (!is_vmalloc_addr(ptr))
|
|
+ return kfree(ptr);
|
|
+
|
|
+ vfree(ptr);
|
|
+}
|
|
+
|
|
+#endif
|
|
diff --git a/include/drm/ttm/ttm_bo_driver.h b/include/drm/ttm/ttm_bo_driver.h
|
|
index 4c4e0f8..fd2c122 100644
|
|
--- a/include/drm/ttm/ttm_bo_driver.h
|
|
+++ b/include/drm/ttm/ttm_bo_driver.h
|
|
@@ -115,7 +115,6 @@ struct ttm_backend {
|
|
struct ttm_backend_func *func;
|
|
};
|
|
|
|
-#define TTM_PAGE_FLAG_VMALLOC (1 << 0)
|
|
#define TTM_PAGE_FLAG_USER (1 << 1)
|
|
#define TTM_PAGE_FLAG_USER_DIRTY (1 << 2)
|
|
#define TTM_PAGE_FLAG_WRITE (1 << 3)
|
|
diff --git a/include/drm/ttm/ttm_page_alloc.h b/include/drm/ttm/ttm_page_alloc.h
|
|
new file mode 100644
|
|
index 0000000..8bb4de5
|
|
--- /dev/null
|
|
+++ b/include/drm/ttm/ttm_page_alloc.h
|
|
@@ -0,0 +1,74 @@
|
|
+/*
|
|
+ * Copyright (c) Red Hat Inc.
|
|
+
|
|
+ * Permission is hereby granted, free of charge, to any person obtaining a
|
|
+ * copy of this software and associated documentation files (the "Software"),
|
|
+ * to deal in the Software without restriction, including without limitation
|
|
+ * the rights to use, copy, modify, merge, publish, distribute, sub license,
|
|
+ * and/or sell copies of the Software, and to permit persons to whom the
|
|
+ * Software is furnished to do so, subject to the following conditions:
|
|
+ *
|
|
+ * The above copyright notice and this permission notice (including the
|
|
+ * next paragraph) shall be included in all copies or substantial portions
|
|
+ * of the Software.
|
|
+ *
|
|
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
|
|
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
+ * DEALINGS IN THE SOFTWARE.
|
|
+ *
|
|
+ * Authors: Dave Airlie <airlied@redhat.com>
|
|
+ * Jerome Glisse <jglisse@redhat.com>
|
|
+ */
|
|
+#ifndef TTM_PAGE_ALLOC
|
|
+#define TTM_PAGE_ALLOC
|
|
+
|
|
+#include "ttm_bo_driver.h"
|
|
+#include "ttm_memory.h"
|
|
+
|
|
+/**
|
|
+ * Get count number of pages from pool to pages list.
|
|
+ *
|
|
+ * @pages: heado of empty linked list where pages are filled.
|
|
+ * @flags: ttm flags for page allocation.
|
|
+ * @cstate: ttm caching state for the page.
|
|
+ * @count: number of pages to allocate.
|
|
+ */
|
|
+int ttm_get_pages(struct list_head *pages,
|
|
+ int flags,
|
|
+ enum ttm_caching_state cstate,
|
|
+ unsigned count);
|
|
+/**
|
|
+ * Put linked list of pages to pool.
|
|
+ *
|
|
+ * @pages: list of pages to free.
|
|
+ * @page_count: number of pages in the list. Zero can be passed for unknown
|
|
+ * count.
|
|
+ * @flags: ttm flags for page allocation.
|
|
+ * @cstate: ttm caching state.
|
|
+ */
|
|
+void ttm_put_pages(struct list_head *pages,
|
|
+ unsigned page_count,
|
|
+ int flags,
|
|
+ enum ttm_caching_state cstate);
|
|
+/**
|
|
+ * Initialize pool allocator.
|
|
+ *
|
|
+ * Pool allocator is internaly reference counted so it can be initialized
|
|
+ * multiple times but ttm_page_alloc_fini has to be called same number of
|
|
+ * times.
|
|
+ */
|
|
+int ttm_page_alloc_init(struct ttm_mem_global *glob, unsigned max_pages);
|
|
+/**
|
|
+ * Free pool allocator.
|
|
+ */
|
|
+void ttm_page_alloc_fini(void);
|
|
+
|
|
+/**
|
|
+ * Output the state of pools to debugfs file
|
|
+ */
|
|
+extern int ttm_page_alloc_debugfs(struct seq_file *m, void *data);
|
|
+#endif
|