import java.io.*
import java.util.*

fun solve(H: Int, W: Int, S: Array<String>) {
    val graph = AdjacencyMatrixGraph<Int, Long>(Array(H*W) { Array<Long?>(H*W) { null } })
    fun idx(h: Int, w: Int) = h * W + w
    for (h in 0 until H) {
        for (w in 0 until W) {
            if (S[h][w] == '#') continue
            val nexts = sequenceOf(h + 1 to w, h to w + 1)
                .filter { it.first < H && it.second < W }
            for (next in nexts) {
                if (S[next.first][next.second] == '.') {
                    graph.addEdge(idx(h, w), idx(next.first, next.second), 1L)
                    graph.addEdge(idx(next.first, next.second), idx(h, w), 1L)
                }
            }
        }
    }
    fun findStarts(): List<Int> {
        val indices = mutableListOf<Int>()
        for (h in 0 until H) {
            for (w in 0 until W) {
                if (S[h][w] == '.') indices.add(idx(h, w))
            }
        }
        return indices
    }
    val starts = findStarts()
    println(starts.map{ BreadthFirstSearch(graph, it).distances.max()!! }.max()!!)
    return
}

fun main(args: Array<String>) {
    val (H, W) = readLine()!!.split(" ").map { it.toInt() }
    val S = Array(H) { readLine()!! }
    solve(H, W, S)
}

interface Graph<T : Comparable<T>, E : Comparable<E>> {
    val size: Int
    fun getWeight(from: Int, to: Int): E?
    fun edgesFrom(from: Int): List<Edge<E>>
    fun addEdge(from: Int, to: Int, weight: E): Boolean
    fun removeEdge(from: Int, to: Int): Boolean
    fun isAdjacent(from: Int, to: Int): Boolean
}

data class Edge<T : Comparable<T>>(val to: Int, val weight: T)
data class Vertex<T : Comparable<T>, E : Comparable<E>>(val data: T) {
    val edges = mutableListOf<Edge<E>>()

    fun addEdge(to: Int, weight: E) = when (edges.find { it.to == to }) {
        null -> {
            edges.add(Edge(to, weight))
            true
        }
        else -> false
    }

    fun removeEdge(to: Int): Boolean {
        for ((i, edge) in edges.withIndex()) {
            if (edge.to == to) {
                edges.removeAt(i)
                return true
            }
        }
        return false
    }
}

class AdjacentListGraph<T : Comparable<T>, E : Comparable<E>>(
    private val vertices: MutableList<Vertex<T, E>>
) : Graph<T, E> {
    override val size = vertices.size
    override fun getWeight(from: Int, to: Int): E? =
        vertices[from].edges.find { it.to == to }?.weight
    override fun edgesFrom(from: Int): List<Edge<E>> = vertices[from].edges
    override fun addEdge(from: Int, to: Int, weight: E) = vertices[from].addEdge(to, weight)
    override fun removeEdge(from: Int, to: Int) = vertices[from].removeEdge(to)
    override fun isAdjacent(from: Int, to: Int) =
        vertices[from].edges.find { it.to == to } != null
}

class AdjacencyMatrixGraph<T : Comparable<T>, E : Comparable<E>>(
    private val matrix: Array<Array<E?>>
) : Graph<T, E> {
    override val size = matrix.size
    override fun getWeight(from: Int, to: Int): E? = matrix[from][to]
    override fun edgesFrom(from: Int): List<Edge<E>> =
        matrix[from].withIndex()
            .filter { it.value != null }
            .map { Edge(it.index, it.value!!) }

    override fun addEdge(from: Int, to: Int, weight: E): Boolean {
        if (matrix[from][to] == null) {
            matrix[from][to] = weight
            return true
        }
        return false
    }

    override fun removeEdge(from: Int, to: Int): Boolean {
        if (from >= size || to >= size || matrix[from][to] == null) {
            return false
        }
        matrix[from][to] = null
        return true
    }

    override fun isAdjacent(from: Int, to: Int): Boolean = matrix[from][to] != null
}

class BreadthFirstSearch<T : Comparable<T>>(val graph: Graph<T, Long>, val from: Int) {
    /**
     * Constraints:
     *   - weights must be same
     *
     * Complexity:
     *   - time: O(V)
     *   - space: O(V)
     */

    val distances = Array(graph.size) { -1L }
    val beforeNodes = IntArray(graph.size) { -1 }
    init {
        search()
    }

    fun search() {
        val deque = ArrayDeque<Int>()
        distances[from] = 0L
        deque.add(from)
        while (deque.isNotEmpty()) {
            val node = deque.removeFirst()
            graph.edgesFrom(node).filter { distances[it.to] == -1L }.forEach {
                distances[it.to] = distances[node] + it.weight
                beforeNodes[it.to] = node
                deque.add(it.to)
            }
        }
    }

    fun shortestPath(to: Int): List<Int> {
        val path = ArrayDeque<Int>()
        var current = to
        path.add(to)
        while (current != from) {
            current = beforeNodes[current]
            path.addFirst(current)
        }
        return path.toList()
    }
}


Measured memory_bytes: 121536