#include <algorithm>
#include <iostream>
#include <vector>
#include <queue>

using namespace std;

typedef long long ll;

template<class T>
inline bool chmax(T &a, T b) {
    if(a < b) {
        a = b;
        return true;
    }
    return false;
}

template<class T>
inline bool chmin(T &a, T b) {
    if(a > b) {
        a = b;
        return true;
    }
    return false;
}

#define MAX_V 100000
#define INF 1000000001

typedef long long ll;

class Elem {
    public:
        int vertex;
        ll distance;

        bool operator < (const Elem &other) const {
            return distance < other.distance;
        }

        bool operator > (const Elem &other) const {
            return distance > other.distance;
        }
};

typedef vector<vector<Elem> > Edges;

typedef priority_queue<Elem, vector<Elem>, greater<Elem> > PriorityQueue;

/*
 * n: size of vertices (assume that names of vertices are 0, 1, ..., n-1)
 * start: start vertex
 */
void dijkstra(const int n, const int start, const Edges &edges,
        vector<ll> &d_list, PriorityQueue &queue) {
    // Initialization
    for (int i = 0; i < n; i++) {
        if (i == start) {
            d_list[i] = 0;
        } else {
            d_list[i] = INF;
        }

        Elem elem;
        elem.vertex = i;
        elem.distance = d_list[i];
        queue.push(elem);
    }

    while (queue.size() > 0) {
        Elem elem = queue.top();
        queue.pop();

        for (int i = 0; i < (int) edges[elem.vertex].size(); i++) {
            Elem next = edges[elem.vertex][i];
            ll alt = d_list[elem.vertex] + next.distance;
            if (d_list[next.vertex] > alt) {
                d_list[next.vertex] = alt;

                Elem e;
                e.vertex = next.vertex;
                e.distance = d_list[next.vertex];
                queue.push(e);
            }
        }
    }
}

Edges edges;

int main(void) {
    int H, W;
    cin >> H >> W;

    vector<vector<char>> maze(H, vector<char>(W, '.'));
    for (int i = 0; i < H; i++) {
        string S;
        cin >> S;
        for (int j = 0; j < W; j++) {
            if (S[j] == '#') {
                maze[i][j] = '#';
            }
        }
    }

#define POS(x, y) ((x) * W + y)

    const int N = H * W;
    edges.resize(N, vector<Elem>());
    for (int i = 0; i < H; i++) {
        for (int j = 0; j < W; j++) {
            // up
            if (i >= 1 && maze[i - 1][j] != '#') {
                edges[POS(i, j)].push_back({POS(i - 1, j), 1});
            }
            // right
            if (j < W - 1 && maze[i][j + 1] != '#') {
                edges[POS(i, j)].push_back({POS(i, j + 1), 1});
            }
            // down
            if (i < H - 1 && maze[i + 1][j] != '#') {
                edges[POS(i, j)].push_back({POS(i + 1, j), 1});
            }
            // left
            if (j >= 1 && maze[i][j - 1] != '#') {
                edges[POS(i, j)].push_back({POS(i, j - 1), 1});
            }
        }
    }

    PriorityQueue q;

    ll max = 0;
    for (int i = 0; i < N; i++) {
        if (maze[i / W][i % W] == '#') continue;
        vector<ll> d_list(N, INF);
        d_list[i] = 0;
        dijkstra(N, i, edges, d_list, q);
        for (int i = 0; i < N; i++) {
            if (d_list[i] != INF) {
                chmax(max, d_list[i]);
            }
        }
    }

    cout << max << endl;

    return 0;
}


Measured memory_bytes: 256